diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c7fc12df9..b6d40a0346 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") # Proposal for C++ unit tests and sandboxes option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF) -option(SLIC3R_BUILD_TESTS "Build unit tests" OFF) +option(SLIC3R_BUILD_TESTS "Build unit tests" ON) # Print out the SLIC3R_* cache options get_cmake_property(_cache_vars CACHE_VARIABLES) @@ -173,10 +173,11 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. add_compile_options(-Werror=return-type) - #removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) - #if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1) - # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM - #endif() + # removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) + # https://eigen.tuxfamily.org/bz/show_bug.cgi?id=1221 + if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.0) + add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM + endif() #GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or @@ -190,6 +191,7 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP add_compile_options(-fsanitize=address -fno-omit-frame-pointer) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fsanitize=address") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan") @@ -254,7 +256,8 @@ if(NOT WIN32) # boost::process was introduced first in version 1.64.0 set(MINIMUM_BOOST_VERSION "1.64.0") endif() -find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS system filesystem thread log locale regex) +set(_boost_components "system;filesystem;thread;log;locale;regex") +find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS ${_boost_components}) add_library(boost_libs INTERFACE) add_library(boost_headeronly INTERFACE) @@ -268,37 +271,55 @@ if(NOT SLIC3R_STATIC) target_compile_definitions(boost_headeronly INTERFACE BOOST_LOG_DYN_LINK) endif() +function(slic3r_remap_configs targets from_Cfg to_Cfg) + if(MSVC) + string(TOUPPER ${from_Cfg} from_CFG) + + foreach(tgt ${targets}) + if(TARGET ${tgt}) + set_target_properties(${tgt} PROPERTIES MAP_IMPORTED_CONFIG_${from_CFG} ${to_Cfg}) + endif() + endforeach() + endif() +endfunction() + if(TARGET Boost::system) message(STATUS "Boost::boost exists") target_link_libraries(boost_headeronly INTERFACE Boost::boost) + + # Only from cmake 3.12 + # list(TRANSFORM _boost_components PREPEND Boost:: OUTPUT_VARIABLE _boost_targets) + set(_boost_targets "") + foreach(comp ${_boost_components}) + list(APPEND _boost_targets "Boost::${comp}") + endforeach() + target_link_libraries(boost_libs INTERFACE boost_headeronly # includes the custom compile definitions as well - Boost::system - Boost::filesystem - Boost::thread - Boost::log - Boost::locale - Boost::regex + ${_boost_targets} ) + slic3r_remap_configs("${_boost_targets}" RelWithDebInfo Release) else() target_include_directories(boost_headeronly INTERFACE ${Boost_INCLUDE_DIRS}) target_link_libraries(boost_libs INTERFACE boost_headeronly ${Boost_LIBRARIES}) endif() + + # Find and configure intel-tbb if(SLIC3R_STATIC) set(TBB_STATIC 1) endif() set(TBB_DEBUG 1) find_package(TBB REQUIRED) -include_directories(${TBB_INCLUDE_DIRS}) -add_definitions(${TBB_DEFINITIONS}) -if(MSVC) - # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. - add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE) -endif() +# include_directories(${TBB_INCLUDE_DIRS}) +# add_definitions(${TBB_DEFINITIONS}) +# if(MSVC) +# # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. +# add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE) +# endif() # The Intel TBB library will use the std::exception_ptr feature of C++11. -add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0) +# add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0) find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) @@ -375,6 +396,16 @@ add_custom_target(pot COMMENT "Generate pot file from strings in the source tree" ) +find_package(NLopt 1.4 REQUIRED) + +if(SLIC3R_STATIC) + set(OPENVDB_USE_STATIC_LIBS ON) + set(USE_BLOSC TRUE) +endif() + +#find_package(OpenVDB 5.0 COMPONENTS openvdb) +#slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release) + # libslic3r, PrusaSlicer GUI and the PrusaSlicer executable. add_subdirectory(src) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT PrusaSlicer_app_console) diff --git a/cmake/modules/Catch2/Catch.cmake b/cmake/modules/Catch2/Catch.cmake new file mode 100644 index 0000000000..0ffe978dc5 --- /dev/null +++ b/cmake/modules/Catch2/Catch.cmake @@ -0,0 +1,175 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +Catch +----- + +This module defines a function to help use the Catch test framework. + +The :command:`catch_discover_tests` discovers tests by asking the compiled test +executable to enumerate its tests. This does not require CMake to be re-run +when tests change. However, it may not work in a cross-compiling environment, +and setting test properties is less convenient. + +This command is intended to replace use of :command:`add_test` to register +tests, and will create a separate CTest test for each Catch test case. Note +that this is in some cases less efficient, as common set-up and tear-down logic +cannot be shared by multiple test cases executing in the same instance. +However, it provides more fine-grained pass/fail information to CTest, which is +usually considered as more beneficial. By default, the CTest test name is the +same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: catch_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + catch_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [TEST_LIST var] + ) + + ``catch_discover_tests`` sets up a post-build command on the test executable + that generates the list of tests by parsing the output from running the test + with the ``--list-test-names-only`` argument. This ensures that the full + list of tests is obtained. Since test discovery occurs at build time, it is + not necessary to re-run CMake when the list of tests changes. + However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set + in order to function in a cross-compiling environment. + + Additionally, setting properties on tests is somewhat less convenient, since + the tests are not available at CMake time. Additional test properties may be + assigned to the set of tests as a whole using the ``PROPERTIES`` option. If + more fine-grained test control is needed, custom content may be provided + through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` + directory property. The set of discovered tests is made accessible to such a + script via the ``_TESTS`` variable. + + The options are: + + ``target`` + Specifies the Catch executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the Catch executable with the ``--list-test-names-only`` argument. + + ``EXTRA_ARGS arg1...`` + Any extra arguments to pass on the command line to each test case. + + ``WORKING_DIRECTORY dir`` + Specifies the directory in which to run the discovered test cases. If this + option is not provided, the current binary directory is used. + + ``TEST_PREFIX prefix`` + Specifies a ``prefix`` to be prepended to the name of each discovered test + case. This can be useful when the same test executable is being used in + multiple calls to ``catch_discover_tests()`` but with different + ``TEST_SPEC`` or ``EXTRA_ARGS``. + + ``TEST_SUFFIX suffix`` + Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of + every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may + be specified. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``catch_discover_tests``. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``_TESTS``. This can be useful when the same test + executable is being used in multiple calls to ``catch_discover_tests()``. + Note that this variable is only available in CTest. + +#]=======================================================================] + +#------------------------------------------------------------------------------ +function(catch_discover_tests TARGET) + cmake_parse_arguments( + "" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" + ${ARGN} + ) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + ## Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + get_property(crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR + ) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=${TARGET}" + -D "TEST_EXECUTABLE=$" + -D "TEST_EXECUTOR=${crosscompiling_emulator}" + -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" + -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" + -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_PREFIX='${_TEST_PREFIX}'" + -D "TEST_SUFFIX='${_TEST_SUFFIX}'" + -D "TEST_LIST=${_TEST_LIST}" + -D "CTEST_FILE=${ctest_tests_file}" + -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM + ) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + if (NOT ${test_include_file_set}) + set_property(DIRECTORY + PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" + ) + else() + message(FATAL_ERROR + "Cannot set more than one TEST_INCLUDE_FILE" + ) + endif() + endif() + +endfunction() + +############################################################################### + +set(_CATCH_DISCOVER_TESTS_SCRIPT + ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake +) diff --git a/cmake/modules/Catch2/CatchAddTests.cmake b/cmake/modules/Catch2/CatchAddTests.cmake new file mode 100644 index 0000000000..ca5ebc17e5 --- /dev/null +++ b/cmake/modules/Catch2/CatchAddTests.cmake @@ -0,0 +1,106 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +set(prefix "${TEST_PREFIX}") +set(suffix "${TEST_SUFFIX}") +set(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(script) +set(suite) +set(tests) + +function(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument + else() + set(_args "${_args} ${_arg}") + endif() + endforeach() + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) +endfunction() + +macro(_add_catch_test_labels LINE) + # convert to list of tags + string(REPLACE "][" "]\\;[" tags ${line}) + + add_command( + set_tests_properties "${prefix}${test}${suffix}" + PROPERTIES + LABELS "${tags}" + ) +endmacro() + +macro(_add_catch_test LINE) + set(test ${line}) + # use escape commas to handle properly test cases with commans inside the name + string(REPLACE "," "\\," test_name ${test}) + # ...and add to script + add_command( + add_test "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "${test_name}" + ${extra_args} + ) + + add_command( + set_tests_properties "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ${properties} + ) + list(APPEND tests "${prefix}${test}${suffix}") +endmacro() + +# Run test executable to get list of available tests +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR + "Specified test executable '${TEST_EXECUTABLE}' does not exist" + ) +endif() +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests + OUTPUT_VARIABLE output + RESULT_VARIABLE result +) +# Catch --list-test-names-only reports the number of tests, so 0 is... surprising +if(${result} EQUAL 0) + message(WARNING + "Test executable '${TEST_EXECUTABLE}' contains no tests!\n" + ) +elseif(${result} LESS 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" + " Output: ${output}\n" + ) +endif() + +string(REPLACE "\n" ";" output "${output}") +set(test) +set(tags_regex "(\\[([^\\[]*)\\])+$") + +# Parse output +foreach(line ${output}) + # lines without leading whitespaces are catch output not tests + if(${line} MATCHES "^[ \t]+") + # strip leading spaces and tabs + string(REGEX REPLACE "^[ \t]+" "" line ${line}) + + if(${line} MATCHES "${tags_regex}") + _add_catch_test_labels(${line}) + else() + _add_catch_test(${line}) + endif() + endif() +endforeach() + +# Create a list of all discovered tests, which users may use to e.g. set +# properties on the tests +add_command(set ${TEST_LIST} ${tests}) + +# Write CTest script +file(WRITE "${CTEST_FILE}" "${script}") diff --git a/cmake/modules/Catch2/ParseAndAddCatchTests.cmake b/cmake/modules/Catch2/ParseAndAddCatchTests.cmake new file mode 100644 index 0000000000..925d932819 --- /dev/null +++ b/cmake/modules/Catch2/ParseAndAddCatchTests.cmake @@ -0,0 +1,225 @@ +#==================================================================================================# +# supported macros # +# - TEST_CASE, # +# - SCENARIO, # +# - TEST_CASE_METHOD, # +# - CATCH_TEST_CASE, # +# - CATCH_SCENARIO, # +# - CATCH_TEST_CASE_METHOD. # +# # +# Usage # +# 1. make sure this module is in the path or add this otherwise: # +# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # +# 2. make sure that you've enabled testing option for the project by the call: # +# enable_testing() # +# 3. add the lines to the script for testing target (sample CMakeLists.txt): # +# project(testing_target) # +# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # +# enable_testing() # +# # +# find_path(CATCH_INCLUDE_DIR "catch.hpp") # +# include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) # +# # +# file(GLOB SOURCE_FILES "*.cpp") # +# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) # +# # +# include(ParseAndAddCatchTests) # +# ParseAndAddCatchTests(${PROJECT_NAME}) # +# # +# The following variables affect the behavior of the script: # +# # +# PARSE_CATCH_TESTS_VERBOSE (Default OFF) # +# -- enables debug messages # +# PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) # +# -- excludes tests marked with [!hide], [.] or [.foo] tags # +# PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) # +# -- adds fixture class name to the test name # +# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) # +# -- adds cmake target name to the test name # +# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # +# -- causes CMake to rerun when file with tests changes so that new tests will be discovered # +# # +# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way # +# a test should be run. For instance to use test MPI, one can write # +# set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) # +# just before calling this ParseAndAddCatchTests function # +# # +# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test # +# command. For example, to include successful tests in the output, one can write # +# set(AdditionalCatchParameters --success) # +# # +# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source # +# file in the target is set, and contains the list of the tests extracted from that target, or # +# from that file. This is useful, for example to add further labels or properties to the tests. # +# # +#==================================================================================================# + +if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) + message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer") +endif() + +option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) +option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) +option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON) +option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) +option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) + +function(ParseAndAddCatchTests_PrintDebugMessage) + if(PARSE_CATCH_TESTS_VERBOSE) + message(STATUS "ParseAndAddCatchTests: ${ARGV}") + endif() +endfunction() + +# This removes the contents between +# - block comments (i.e. /* ... */) +# - full line comments (i.e. // ... ) +# contents have been read into '${CppCode}'. +# !keep partial line comments +function(ParseAndAddCatchTests_RemoveComments CppCode) + string(ASCII 2 CMakeBeginBlockComment) + string(ASCII 3 CMakeEndBlockComment) + string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") + string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}") + string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}") + string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}") + + set(${CppCode} "${${CppCode}}" PARENT_SCOPE) +endfunction() + +# Worker function +function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget) + # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file. + if(SourceFile MATCHES "\\\$") + ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.") + return() + endif() + # According to CMake docs EXISTS behavior is well-defined only for full paths. + get_filename_component(SourceFile ${SourceFile} ABSOLUTE) + if(NOT EXISTS ${SourceFile}) + message(WARNING "Cannot find source file: ${SourceFile}") + return() + endif() + ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") + file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) + + # Remove block and fullline comments + ParseAndAddCatchTests_RemoveComments(Contents) + + # Find definition of test names + string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") + + if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) + ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") + set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} + ) + endif() + + foreach(TestName ${Tests}) + # Strip newlines + string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") + + # Get test type and fixture if applicable + string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") + string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") + string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") + + # Get string parts of test definition + string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") + + # Strip wrapping quotation marks + string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") + string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") + + # Validate that a test name and tags have been provided + list(LENGTH TestStrings TestStringsLength) + if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) + message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") + endif() + + # Assign name and tags + list(GET TestStrings 0 Name) + if("${TestType}" STREQUAL "SCENARIO") + set(Name "Scenario: ${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture) + set(CTestName "${TestFixture}:${Name}") + else() + set(CTestName "${Name}") + endif() + if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) + set(CTestName "${TestTarget}:${CTestName}") + endif() + # add target to labels to enable running all tests added from this target + set(Labels ${TestTarget}) + if(TestStringsLength EQUAL 2) + list(GET TestStrings 1 Tags) + string(TOLOWER "${Tags}" Tags) + # remove target from labels if the test is hidden + if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") + list(REMOVE_ITEM Labels ${TestTarget}) + endif() + string(REPLACE "]" ";" Tags "${Tags}") + string(REPLACE "[" "" Tags "${Tags}") + else() + # unset tags variable from previous loop + unset(Tags) + endif() + + list(APPEND Labels ${Tags}) + + set(HiddenTagFound OFF) + foreach(label ${Labels}) + string(REGEX MATCH "^!hide|^\\." result ${label}) + if(result) + set(HiddenTagFound ON) + break() + endif(result) + endforeach(label) + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") + ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") + else() + ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") + if(Labels) + ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") + endif() + + # Escape commas in the test spec + string(REPLACE "," "\\," Name ${Name}) + + # Add the test and set its properties + add_test(NAME "\"${CTestName}\"" COMMAND ${OptionalCatchTestLauncher} $ ${Name} ${AdditionalCatchParameters}) + # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead + if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8") + ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property") + set_tests_properties("\"${CTestName}\"" PROPERTIES DISABLED ON) + else() + set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" + LABELS "${Labels}") + endif() + set_property( + TARGET ${TestTarget} + APPEND + PROPERTY ParseAndAddCatchTests_TESTS "\"${CTestName}\"") + set_property( + SOURCE ${SourceFile} + APPEND + PROPERTY ParseAndAddCatchTests_TESTS "\"${CTestName}\"") + endif() + + + endforeach() +endfunction() + +# entry point +function(ParseAndAddCatchTests TestTarget) + ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") + get_target_property(SourceFiles ${TestTarget} SOURCES) + ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") + foreach(SourceFile ${SourceFiles}) + ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) + endforeach() + ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") +endfunction() diff --git a/src/libnest2d/cmake_modules/FindNLopt.cmake b/cmake/modules/FindNLopt.cmake similarity index 92% rename from src/libnest2d/cmake_modules/FindNLopt.cmake rename to cmake/modules/FindNLopt.cmake index 2f813b6aab..912ce8d30a 100644 --- a/src/libnest2d/cmake_modules/FindNLopt.cmake +++ b/cmake/modules/FindNLopt.cmake @@ -21,8 +21,7 @@ set(NLopt_FOUND FALSE) set(NLopt_ERROR_REASON "") set(NLopt_DEFINITIONS "") -set(NLopt_LIBS) - +unset(NLopt_LIBS CACHE) set(NLopt_DIR $ENV{NLOPT}) if(NOT NLopt_DIR) @@ -48,15 +47,14 @@ if(NOT NLopt_DIR) set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}'.") endif() unset(_NLopt_HEADER_FILE_NAME) - unset(_NLopt_HEADER_FILE) - + if(NOT NLopt_FOUND) set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} NLopt not found in system directories (and environment variable NLOPT is not set).") else() get_filename_component(NLopt_INCLUDE_DIR ${_NLopt_HEADER_FILE} DIRECTORY ) endif() - + unset(_NLopt_HEADER_FILE CACHE) else() @@ -95,7 +93,7 @@ else() set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}' in '${NLopt_INCLUDE_DIR}'.") endif() unset(_NLopt_HEADER_FILE_NAME) - unset(_NLopt_HEADER_FILE) + unset(_NLopt_HEADER_FILE CACHE) endif() @@ -114,10 +112,10 @@ if(NLopt_FOUND) message(STATUS "Found NLopt in '${NLopt_DIR}'.") message(STATUS "Using NLopt include directory '${NLopt_INCLUDE_DIR}'.") message(STATUS "Using NLopt library '${NLopt_LIBS}'.") - add_library(Nlopt::Nlopt INTERFACE IMPORTED) - set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_LINK_LIBRARIES ${NLopt_LIBS}) - set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${NLopt_INCLUDE_DIR}) - set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${NLopt_DEFINITIONS}") + add_library(NLopt::nlopt INTERFACE IMPORTED) + set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_LINK_LIBRARIES ${NLopt_LIBS}) + set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${NLopt_INCLUDE_DIR}) + set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${NLopt_DEFINITIONS}") # target_link_libraries(Nlopt::Nlopt INTERFACE ${NLopt_LIBS}) # target_include_directories(Nlopt::Nlopt INTERFACE ${NLopt_INCLUDE_DIR}) # target_compile_definitions(Nlopt::Nlopt INTERFACE ${NLopt_DEFINITIONS}) diff --git a/cmake/modules/FindOpenVDB.cmake b/cmake/modules/FindOpenVDB.cmake new file mode 100644 index 0000000000..9afe8a2356 --- /dev/null +++ b/cmake/modules/FindOpenVDB.cmake @@ -0,0 +1,490 @@ +# Copyright (c) DreamWorks Animation LLC +# +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +# +# Redistributions of source code must retain the above copyright +# and license notice and the following restrictions and disclaimer. +# +# * Neither the name of DreamWorks Animation nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +# +#[=======================================================================[.rst: + +FindOpenVDB +----------- + +Find OpenVDB include dirs, libraries and settings + +Use this module by invoking find_package with the form:: + + find_package(OpenVDB + [version] [EXACT] # Minimum or EXACT version + [REQUIRED] # Fail with error if OpenVDB is not found + [COMPONENTS ...] # OpenVDB libraries by their canonical name + # e.g. "openvdb" for "libopenvdb" + ) + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +``OpenVDB::openvdb`` + The core openvdb library target. + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``OpenVDB_FOUND`` + True if the system has the OpenVDB library. +``OpenVDB_VERSION`` + The version of the OpenVDB library which was found. +``OpenVDB_INCLUDE_DIRS`` + Include directories needed to use OpenVDB. +``OpenVDB_LIBRARIES`` + Libraries needed to link to OpenVDB. +``OpenVDB_LIBRARY_DIRS`` + OpenVDB library directories. +``OpenVDB_DEFINITIONS`` + Definitions to use when compiling code that uses OpenVDB. +``OpenVDB_{COMPONENT}_FOUND`` + True if the system has the named OpenVDB component. +``OpenVDB_USES_BLOSC`` + True if the OpenVDB Library has been built with blosc support +``OpenVDB_USES_LOG4CPLUS`` + True if the OpenVDB Library has been built with log4cplus support +``OpenVDB_USES_EXR`` + True if the OpenVDB Library has been built with openexr support +``OpenVDB_ABI`` + Set if this module was able to determine the ABI number the located + OpenVDB Library was built against. Unset otherwise. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``OpenVDB_INCLUDE_DIR`` + The directory containing ``openvdb/version.h``. +``OpenVDB_{COMPONENT}_LIBRARY`` + Individual component libraries for OpenVDB + +Hints +^^^^^ + +Instead of explicitly setting the cache variables, the following variables +may be provided to tell this module where to look. + +``OPENVDB_ROOT`` + Preferred installation prefix. +``OPENVDB_INCLUDEDIR`` + Preferred include directory e.g. /include +``OPENVDB_LIBRARYDIR`` + Preferred library directory e.g. /lib +``SYSTEM_LIBRARY_PATHS`` + Paths appended to all include and lib searches. + +#]=======================================================================] + +cmake_minimum_required(VERSION 3.3) +# Monitoring _ROOT variables +if(POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) +endif() + +# Include utility functions for version information +include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake) + +mark_as_advanced( + OpenVDB_INCLUDE_DIR + OpenVDB_LIBRARY +) + +set(_OPENVDB_COMPONENT_LIST + openvdb +) + +if(OpenVDB_FIND_COMPONENTS) + set(OPENVDB_COMPONENTS_PROVIDED TRUE) + set(_IGNORED_COMPONENTS "") + foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) + if(NOT ${COMPONENT} IN_LIST _OPENVDB_COMPONENT_LIST) + list(APPEND _IGNORED_COMPONENTS ${COMPONENT}) + endif() + endforeach() + + if(_IGNORED_COMPONENTS) + message(STATUS "Ignoring unknown components of OpenVDB:") + foreach(COMPONENT ${_IGNORED_COMPONENTS}) + message(STATUS " ${COMPONENT}") + endforeach() + list(REMOVE_ITEM OpenVDB_FIND_COMPONENTS ${_IGNORED_COMPONENTS}) + endif() +else() + set(OPENVDB_COMPONENTS_PROVIDED FALSE) + set(OpenVDB_FIND_COMPONENTS ${_OPENVDB_COMPONENT_LIST}) +endif() + +# Append OPENVDB_ROOT or $ENV{OPENVDB_ROOT} if set (prioritize the direct cmake var) +set(_OPENVDB_ROOT_SEARCH_DIR "") + +# Additionally try and use pkconfig to find OpenVDB + +find_package(PkgConfig) +pkg_check_modules(PC_OpenVDB QUIET OpenVDB) + +# ------------------------------------------------------------------------ +# Search for OpenVDB include DIR +# ------------------------------------------------------------------------ + +set(_OPENVDB_INCLUDE_SEARCH_DIRS "") +list(APPEND _OPENVDB_INCLUDE_SEARCH_DIRS + ${OPENVDB_INCLUDEDIR} + ${_OPENVDB_ROOT_SEARCH_DIR} + ${PC_OpenVDB_INCLUDE_DIRS} + ${SYSTEM_LIBRARY_PATHS} +) + +# Look for a standard OpenVDB header file. +find_path(OpenVDB_INCLUDE_DIR openvdb/version.h + PATHS ${_OPENVDB_INCLUDE_SEARCH_DIRS} + PATH_SUFFIXES include +) + +OPENVDB_VERSION_FROM_HEADER("${OpenVDB_INCLUDE_DIR}/openvdb/version.h" + VERSION OpenVDB_VERSION + MAJOR OpenVDB_MAJOR_VERSION + MINOR OpenVDB_MINOR_VERSION + PATCH OpenVDB_PATCH_VERSION +) + +# ------------------------------------------------------------------------ +# Search for OPENVDB lib DIR +# ------------------------------------------------------------------------ + +set(_OPENVDB_LIBRARYDIR_SEARCH_DIRS "") + +# Append to _OPENVDB_LIBRARYDIR_SEARCH_DIRS in priority order + +list(APPEND _OPENVDB_LIBRARYDIR_SEARCH_DIRS + ${OPENVDB_LIBRARYDIR} + ${_OPENVDB_ROOT_SEARCH_DIR} + ${PC_OpenVDB_LIBRARY_DIRS} + ${SYSTEM_LIBRARY_PATHS} +) + +# Build suffix directories + +set(OPENVDB_PATH_SUFFIXES + lib64 + lib +) + +# Static library setup +if(UNIX AND OPENVDB_USE_STATIC_LIBS) + set(_OPENVDB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +endif() + +set(OpenVDB_LIB_COMPONENTS "") + +foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) + set(LIB_NAME ${COMPONENT}) + find_library(OpenVDB_${COMPONENT}_LIBRARY ${LIB_NAME} lib${LIB_NAME} + PATHS ${_OPENVDB_LIBRARYDIR_SEARCH_DIRS} + PATH_SUFFIXES ${OPENVDB_PATH_SUFFIXES} + ) + list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY}) + + if(OpenVDB_${COMPONENT}_LIBRARY) + set(OpenVDB_${COMPONENT}_FOUND TRUE) + else() + set(OpenVDB_${COMPONENT}_FOUND FALSE) + endif() +endforeach() + +if(UNIX AND OPENVDB_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${_OPENVDB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) + unset(_OPENVDB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) +endif() + +# ------------------------------------------------------------------------ +# Cache and set OPENVDB_FOUND +# ------------------------------------------------------------------------ + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenVDB + FOUND_VAR OpenVDB_FOUND + REQUIRED_VARS + OpenVDB_INCLUDE_DIR + OpenVDB_LIB_COMPONENTS + VERSION_VAR OpenVDB_VERSION + HANDLE_COMPONENTS +) + +# ------------------------------------------------------------------------ +# Determine ABI number +# ------------------------------------------------------------------------ + +# Set the ABI number the library was built against. Uses vdb_print +find_program(OPENVDB_PRINT vdb_print PATHS ${OpenVDB_INCLUDE_DIR} ) + +OPENVDB_ABI_VERSION_FROM_PRINT( + "${OPENVDB_PRINT}" + ABI OpenVDB_ABI +) + +if(NOT OpenVDB_FIND_QUIET) + if(NOT OpenVDB_ABI) + message(WARNING "Unable to determine OpenVDB ABI version from OpenVDB " + "installation. The library major version \"${OpenVDB_MAJOR_VERSION}\" " + "will be inferred. If this is not correct, use " + "add_definitions(-DOPENVDB_ABI_VERSION_NUMBER=N)" + ) + else() + message(STATUS "OpenVDB ABI Version: ${OpenVDB_ABI}") + endif() +endif() + +# ------------------------------------------------------------------------ +# Handle OpenVDB dependencies +# ------------------------------------------------------------------------ + +# Add standard dependencies + +find_package(IlmBase COMPONENTS Half) +if(NOT IlmBase_FOUND) + pkg_check_modules(IlmBase QUIET IlmBase) +endif() +if (IlmBase_FOUND AND NOT TARGET IlmBase::Half) + message(STATUS "Falling back to IlmBase found by pkg-config...") + + find_library(IlmHalf_LIBRARY NAMES Half) + if(IlmHalf_LIBRARY-NOTFOUND) + message(FATAL_ERROR "IlmBase::Half can not be found!") + endif() + + add_library(IlmBase::Half UNKNOWN IMPORTED) + set_target_properties(IlmBase::Half PROPERTIES + IMPORTED_LOCATION "${IlmHalf_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES ${IlmBase_INCLUDE_DIRS}) +elseif(NOT IlmBase_FOUND) + message(FATAL_ERROR "IlmBase::Half can not be found!") +endif() +find_package(TBB REQUIRED COMPONENTS tbb) +find_package(ZLIB REQUIRED) +find_package(Boost REQUIRED COMPONENTS iostreams system) + +# Use GetPrerequisites to see which libraries this OpenVDB lib has linked to +# which we can query for optional deps. This basically runs ldd/otoll/objdump +# etc to track deps. We could use a vdb_config binary tools here to improve +# this process + +include(GetPrerequisites) + +set(_EXCLUDE_SYSTEM_PREREQUISITES 1) +set(_RECURSE_PREREQUISITES 0) +set(_OPENVDB_PREREQUISITE_LIST) + +if(NOT OPENVDB_USE_STATIC_LIBS) +get_prerequisites(${OpenVDB_openvdb_LIBRARY} + _OPENVDB_PREREQUISITE_LIST + ${_EXCLUDE_SYSTEM_PREREQUISITES} + ${_RECURSE_PREREQUISITES} + "" + "${SYSTEM_LIBRARY_PATHS}" +) +endif() + +unset(_EXCLUDE_SYSTEM_PREREQUISITES) +unset(_RECURSE_PREREQUISITES) + +# As the way we resolve optional libraries relies on library file names, use +# the configuration options from the main CMakeLists.txt to allow users +# to manually identify the requirements of OpenVDB builds if they know them. + +set(OpenVDB_USES_BLOSC ${USE_BLOSC}) +set(OpenVDB_USES_LOG4CPLUS ${USE_LOG4CPLUS}) +set(OpenVDB_USES_ILM ${USE_EXR}) +set(OpenVDB_USES_EXR ${USE_EXR}) + +# Search for optional dependencies + +foreach(PREREQUISITE ${_OPENVDB_PREREQUISITE_LIST}) + set(_HAS_DEP) + get_filename_component(PREREQUISITE ${PREREQUISITE} NAME) + + string(FIND ${PREREQUISITE} "blosc" _HAS_DEP) + if(NOT ${_HAS_DEP} EQUAL -1) + set(OpenVDB_USES_BLOSC ON) + endif() + + string(FIND ${PREREQUISITE} "log4cplus" _HAS_DEP) + if(NOT ${_HAS_DEP} EQUAL -1) + set(OpenVDB_USES_LOG4CPLUS ON) + endif() + + string(FIND ${PREREQUISITE} "IlmImf" _HAS_DEP) + if(NOT ${_HAS_DEP} EQUAL -1) + set(OpenVDB_USES_ILM ON) + endif() +endforeach() + +unset(_OPENVDB_PREREQUISITE_LIST) +unset(_HAS_DEP) + +if(OpenVDB_USES_BLOSC) + find_package(Blosc ) + if(NOT Blosc_FOUND OR NOT TARGET Blosc::blosc) + message(STATUS "find_package could not find Blosc. Using fallback blosc search...") + find_path(Blosc_INCLUDE_DIR blosc.h) + find_library(Blosc_LIBRARY NAMES blosc) + if (Blosc_INCLUDE_DIR AND Blosc_LIBRARY) + set(Blosc_FOUND TRUE) + add_library(Blosc::blosc UNKNOWN IMPORTED) + set_target_properties(Blosc::blosc PROPERTIES + IMPORTED_LOCATION "${Blosc_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES ${Blosc_INCLUDE_DIR}) + elseif() + message(FATAL_ERROR "Blosc library can not be found!") + endif() + endif() +endif() + +if(OpenVDB_USES_LOG4CPLUS) + find_package(Log4cplus REQUIRED) +endif() + +if(OpenVDB_USES_ILM) + find_package(IlmBase REQUIRED) +endif() + +if(OpenVDB_USES_EXR) + find_package(OpenEXR REQUIRED) +endif() + +if(UNIX) + find_package(Threads REQUIRED) +endif() + +# Set deps. Note that the order here is important. If we're building against +# Houdini 17.5 we must include OpenEXR and IlmBase deps first to ensure the +# users chosen namespaced headers are correctly prioritized. Otherwise other +# include paths from shared installs (including houdini) may pull in the wrong +# headers + +set(_OPENVDB_VISIBLE_DEPENDENCIES + Boost::iostreams + Boost::system + IlmBase::Half +) + +set(_OPENVDB_DEFINITIONS) +if(OpenVDB_ABI) + list(APPEND _OPENVDB_DEFINITIONS "-DOPENVDB_ABI_VERSION_NUMBER=${OpenVDB_ABI}") +endif() + +if(OpenVDB_USES_EXR) + list(APPEND _OPENVDB_VISIBLE_DEPENDENCIES + IlmBase::IlmThread + IlmBase::Iex + IlmBase::Imath + OpenEXR::IlmImf + ) + list(APPEND _OPENVDB_DEFINITIONS "-DOPENVDB_TOOLS_RAYTRACER_USE_EXR") +endif() + +if(OpenVDB_USES_LOG4CPLUS) + list(APPEND _OPENVDB_VISIBLE_DEPENDENCIES Log4cplus::log4cplus) + list(APPEND _OPENVDB_DEFINITIONS "-DOPENVDB_USE_LOG4CPLUS") +endif() + +list(APPEND _OPENVDB_VISIBLE_DEPENDENCIES + TBB::tbb +) +if(UNIX) + list(APPEND _OPENVDB_VISIBLE_DEPENDENCIES + Threads::Threads + ) +endif() + +set(_OPENVDB_HIDDEN_DEPENDENCIES) + +if(OpenVDB_USES_BLOSC) + if(OPENVDB_USE_STATIC_LIBS) + list(APPEND _OPENVDB_VISIBLE_DEPENDENCIES $) + else() + list(APPEND _OPENVDB_HIDDEN_DEPENDENCIES Blosc::blosc) + endif() +endif() + +if(OPENVDB_USE_STATIC_LIBS) + list(APPEND _OPENVDB_VISIBLE_DEPENDENCIES $) +else() + list(APPEND _OPENVDB_HIDDEN_DEPENDENCIES ZLIB::ZLIB) +endif() + +# ------------------------------------------------------------------------ +# Configure imported target +# ------------------------------------------------------------------------ + +set(OpenVDB_LIBRARIES + ${OpenVDB_LIB_COMPONENTS} +) +set(OpenVDB_INCLUDE_DIRS ${OpenVDB_INCLUDE_DIR}) + +set(OpenVDB_DEFINITIONS) +list(APPEND OpenVDB_DEFINITIONS "${PC_OpenVDB_CFLAGS_OTHER}") +list(APPEND OpenVDB_DEFINITIONS "${_OPENVDB_DEFINITIONS}") +list(REMOVE_DUPLICATES OpenVDB_DEFINITIONS) + +set(OpenVDB_LIBRARY_DIRS "") +foreach(LIB ${OpenVDB_LIB_COMPONENTS}) + get_filename_component(_OPENVDB_LIBDIR ${LIB} DIRECTORY) + list(APPEND OpenVDB_LIBRARY_DIRS ${_OPENVDB_LIBDIR}) +endforeach() +list(REMOVE_DUPLICATES OpenVDB_LIBRARY_DIRS) + +foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) + if(NOT TARGET OpenVDB::${COMPONENT}) + add_library(OpenVDB::${COMPONENT} UNKNOWN IMPORTED) + set_target_properties(OpenVDB::${COMPONENT} PROPERTIES + IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${OpenVDB_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${OpenVDB_INCLUDE_DIR}" + IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps + INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers) + INTERFACE_COMPILE_FEATURES cxx_std_11 + ) + + if (OPENVDB_USE_STATIC_LIBS) + set_target_properties(OpenVDB::${COMPONENT} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "OPENVDB_STATICLIB;OPENVDB_OPENEXR_STATICLIB" + ) + endif() + endif() +endforeach() + +if(OpenVDB_FOUND AND NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + message(STATUS "OpenVDB libraries: ${OpenVDB_LIBRARIES}") +endif() + +unset(_OPENVDB_DEFINITIONS) +unset(_OPENVDB_VISIBLE_DEPENDENCIES) +unset(_OPENVDB_HIDDEN_DEPENDENCIES) diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake index e5115ab443..c6bdec9852 100644 --- a/cmake/modules/FindTBB.cmake +++ b/cmake/modules/FindTBB.cmake @@ -93,8 +93,16 @@ # This module will also create the "tbb" target that may be used when building # executables and libraries. +unset(TBB_FOUND CACHE) +unset(TBB_INCLUDE_DIRS CACHE) +unset(TBB_LIBRARIES) +unset(TBB_LIBRARIES_DEBUG) +unset(TBB_LIBRARIES_RELEASE) + include(FindPackageHandleStandardArgs) +find_package(Threads QUIET REQUIRED) + if(NOT TBB_FOUND) ################################## @@ -215,6 +223,9 @@ if(NOT TBB_FOUND) foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") + unset(TBB_${_comp}_LIBRARY_DEBUG CACHE) + unset(TBB_${_comp}_LIBRARY_RELEASE CACHE) + # Search for the libraries find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} @@ -250,28 +261,31 @@ if(NOT TBB_FOUND) endif() endforeach() - unset(TBB_STATIC_SUFFIX) - ################################## # Set compile flags and libraries ################################## set(TBB_DEFINITIONS_RELEASE "") - set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") + set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1") if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) - set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") - elseif(TBB_LIBRARIES_RELEASE) - set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}") - set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}") - elseif(TBB_LIBRARIES_DEBUG) - set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}") - set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}") 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) @@ -280,25 +294,20 @@ if(NOT TBB_FOUND) ################################## if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) - add_library(tbb UNKNOWN IMPORTED) - set_target_properties(tbb PROPERTIES + 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 PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" + set_target_properties(TBB::tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$,$>:${TBB_DEFINITIONS_DEBUG}>;$<$:${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} ) - elseif(TBB_LIBRARIES_RELEASE) - set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) - else() - set_target_properties(tbb PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" - IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} - ) endif() endif() diff --git a/cmake/modules/OpenVDBUtils.cmake b/cmake/modules/OpenVDBUtils.cmake new file mode 100644 index 0000000000..bb3ce6e65d --- /dev/null +++ b/cmake/modules/OpenVDBUtils.cmake @@ -0,0 +1,166 @@ +# Copyright (c) DreamWorks Animation LLC +# +# All rights reserved. This software is distributed under the +# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +# +# Redistributions of source code must retain the above copyright +# and license notice and the following restrictions and disclaimer. +# +# * Neither the name of DreamWorks Animation nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +# +#[=======================================================================[.rst: + +OpenVDBUtils.cmake +------------------ + +A utility CMake file which provides helper functions for configuring an +OpenVDB installation. + +Use this module by invoking include with the form:: + + include ( OpenVDBUtils ) + + +The following functions are provided: + +``OPENVDB_VERSION_FROM_HEADER`` + + OPENVDB_VERSION_FROM_HEADER ( + VERSION [] + MAJOR [] + MINOR [] + PATCH [] ) + + Parse the provided version file to retrieve the current OpenVDB + version information. The file is expected to be a version.h file + as found in the following path of an OpenVDB repository: + openvdb/version.h + + If the file does not exist, variables are unmodified. + +``OPENVDB_ABI_VERSION_FROM_PRINT`` + + OPENVDB_ABI_VERSION_FROM_PRINT ( + [QUIET] + ABI [] ) + + Retrieve the ABI version that an installation of OpenVDB was compiled + for using the provided vdb_print binary. Parses the result of: + vdb_print --version + + If the binary does not exist or fails to launch, variables are + unmodified. + +#]=======================================================================] + + +function(OPENVDB_VERSION_FROM_HEADER OPENVDB_VERSION_FILE) + cmake_parse_arguments(_VDB "" "VERSION;MAJOR;MINOR;PATCH" "" ${ARGN}) + + if(NOT EXISTS ${OPENVDB_VERSION_FILE}) + return() + endif() + + file(STRINGS "${OPENVDB_VERSION_FILE}" openvdb_version_str + REGEX "^#define[\t ]+OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER[\t ]+.*" + ) + string(REGEX REPLACE "^.*OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER[\t ]+([0-9]*).*$" "\\1" + _OpenVDB_MAJOR_VERSION "${openvdb_version_str}" + ) + + file(STRINGS "${OPENVDB_VERSION_FILE}" openvdb_version_str + REGEX "^#define[\t ]+OPENVDB_LIBRARY_MINOR_VERSION_NUMBER[\t ]+.*" + ) + string(REGEX REPLACE "^.*OPENVDB_LIBRARY_MINOR_VERSION_NUMBER[\t ]+([0-9]*).*$" "\\1" + _OpenVDB_MINOR_VERSION "${openvdb_version_str}" + ) + + file(STRINGS "${OPENVDB_VERSION_FILE}" openvdb_version_str + REGEX "^#define[\t ]+OPENVDB_LIBRARY_PATCH_VERSION_NUMBER[\t ]+.*" + ) + string(REGEX REPLACE "^.*OPENVDB_LIBRARY_PATCH_VERSION_NUMBER[\t ]+([0-9]*).*$" "\\1" + _OpenVDB_PATCH_VERSION "${openvdb_version_str}" + ) + unset(openvdb_version_str) + + if(_VDB_VERSION) + set(${_VDB_VERSION} + ${_OpenVDB_MAJOR_VERSION}.${_OpenVDB_MINOR_VERSION}.${_OpenVDB_PATCH_VERSION} + PARENT_SCOPE + ) + endif() + if(_VDB_MAJOR) + set(${_VDB_MAJOR} ${_OpenVDB_MAJOR_VERSION} PARENT_SCOPE) + endif() + if(_VDB_MINOR) + set(${_VDB_MINOR} ${_OpenVDB_MINOR_VERSION} PARENT_SCOPE) + endif() + if(_VDB_PATCH) + set(${_VDB_PATCH} ${_OpenVDB_PATCH_VERSION} PARENT_SCOPE) + endif() +endfunction() + + +######################################################################## +######################################################################## + + +function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT) + cmake_parse_arguments(_VDB "QUIET" "ABI" "" ${ARGN}) + + if(NOT EXISTS ${OPENVDB_PRINT}) + message(WARNING "vdb_print not found! ${OPENVDB_PRINT}") + return() + endif() + + set(_VDB_PRINT_VERSION_STRING "") + set(_VDB_PRINT_RETURN_STATUS "") + + if(${_VDB_QUIET}) + execute_process(COMMAND ${OPENVDB_PRINT} "--version" + RESULT_VARIABLE _VDB_PRINT_RETURN_STATUS + OUTPUT_VARIABLE _VDB_PRINT_VERSION_STRING + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else() + execute_process(COMMAND ${OPENVDB_PRINT} "--version" + RESULT_VARIABLE _VDB_PRINT_RETURN_STATUS + OUTPUT_VARIABLE _VDB_PRINT_VERSION_STRING + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() + + if(${_VDB_PRINT_RETURN_STATUS}) + message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}") + return() + endif() + + set(_OpenVDB_ABI) + string(REGEX REPLACE ".*abi([0-9]*).*" "\\1" _OpenVDB_ABI ${_VDB_PRINT_VERSION_STRING}) + if(${_OpenVDB_ABI} STREQUAL ${_VDB_PRINT_VERSION_STRING}) + set(_OpenVDB_ABI "") + endif() + unset(_VDB_PRINT_RETURN_STATUS) + unset(_VDB_PRINT_VERSION_STRING) + + if(_VDB_ABI) + set(${_VDB_ABI} ${_OpenVDB_ABI} PARENT_SCOPE) + endif() +endfunction() diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 90ad6f0fa7..49e8ee7bab 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -72,7 +72,7 @@ elseif (APPLE) message(FATAL_ERROR "Could not determine OS X SDK version. Please use -DCMAKE_OSX_DEPLOYMENT_TARGET=") endif () - message("OS X Deployment Target (inferred from default): ${DEP_OSX_TARGET}") + message("OS X Deployment Target (inferred from SDK): ${DEP_OSX_TARGET}") endif () include("deps-macos.cmake") @@ -96,6 +96,7 @@ if (MSVC) dep_nlopt # dep_qhull # Experimental dep_zlib # on Windows we still need zlib + dep_openvdb ) else() @@ -110,6 +111,7 @@ else() dep_cereal dep_nlopt dep_qhull + dep_openvdb # dep_libigl # Not working, static build has different Eigen ) diff --git a/deps/blosc-mods.patch b/deps/blosc-mods.patch new file mode 100644 index 0000000000..9a91b4974c --- /dev/null +++ b/deps/blosc-mods.patch @@ -0,0 +1,468 @@ +From 5669891dfaaa4c814f3ec667ca6bf4e693aea978 Mon Sep 17 00:00:00 2001 +From: tamasmeszaros +Date: Wed, 30 Oct 2019 12:54:52 +0100 +Subject: [PATCH] Blosc 1.17 fixes and cmake config script + +--- + CMakeLists.txt | 105 +++++++++++++++++----------------- + blosc/CMakeLists.txt | 118 +++++++++------------------------------ + cmake/FindLZ4.cmake | 6 +- + cmake/FindSnappy.cmake | 8 ++- + cmake/FindZstd.cmake | 8 ++- + cmake_config.cmake.in | 24 ++++++++ + internal-complibs/CMakeLists.txt | 35 ++++++++++++ + 7 files changed, 157 insertions(+), 147 deletions(-) + create mode 100644 cmake_config.cmake.in + create mode 100644 internal-complibs/CMakeLists.txt + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 59d9fab..e9134c2 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -71,7 +71,7 @@ + # DEV: static includes blosc.a and blosc.h + + +-cmake_minimum_required(VERSION 2.8.12) ++cmake_minimum_required(VERSION 3.1) # Threads::Threads target available from 3.1 + if (NOT CMAKE_VERSION VERSION_LESS 3.3) + cmake_policy(SET CMP0063 NEW) + endif() +@@ -124,55 +124,30 @@ option(PREFER_EXTERNAL_ZSTD + + set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +- +-if(NOT DEACTIVATE_LZ4) +- if(PREFER_EXTERNAL_LZ4) +- find_package(LZ4) +- else() +- message(STATUS "Using LZ4 internal sources.") +- endif(PREFER_EXTERNAL_LZ4) +- # HAVE_LZ4 will be set to true because even if the library is +- # not found, we will use the included sources for it +- set(HAVE_LZ4 TRUE) +-endif(NOT DEACTIVATE_LZ4) +- +-if(NOT DEACTIVATE_SNAPPY) +- if(PREFER_EXTERNAL_SNAPPY) +- find_package(Snappy) +- else() +- message(STATUS "Using Snappy internal sources.") +- endif(PREFER_EXTERNAL_SNAPPY) +- # HAVE_SNAPPY will be set to true because even if the library is not found, +- # we will use the included sources for it +- set(HAVE_SNAPPY TRUE) +-endif(NOT DEACTIVATE_SNAPPY) +- +-if(NOT DEACTIVATE_ZLIB) +- # import the ZLIB_ROOT environment variable to help finding the zlib library +- if(PREFER_EXTERNAL_ZLIB) +- set(ZLIB_ROOT $ENV{ZLIB_ROOT}) +- find_package(ZLIB) +- if (NOT ZLIB_FOUND ) +- message(STATUS "No zlib found. Using internal sources.") +- endif (NOT ZLIB_FOUND ) +- else() +- message(STATUS "Using zlib internal sources.") +- endif(PREFER_EXTERNAL_ZLIB) +- # HAVE_ZLIB will be set to true because even if the library is not found, +- # we will use the included sources for it +- set(HAVE_ZLIB TRUE) +-endif(NOT DEACTIVATE_ZLIB) +- +-if (NOT DEACTIVATE_ZSTD) +- if (PREFER_EXTERNAL_ZSTD) +- find_package(Zstd) +- else () +- message(STATUS "Using ZSTD internal sources.") +- endif (PREFER_EXTERNAL_ZSTD) +- # HAVE_ZSTD will be set to true because even if the library is +- # not found, we will use the included sources for it +- set(HAVE_ZSTD TRUE) +-endif (NOT DEACTIVATE_ZSTD) ++set(LIBS "") ++macro(use_package _pkg _tgt) ++ string(TOUPPER ${_pkg} _PKG) ++ if(NOT DEACTIVATE_${_PKG}) ++ if(PREFER_EXTERNAL_${_PKG}) ++ find_package(${_pkg}) ++ if (NOT ${_pkg}_FOUND ) ++ message(STATUS "No ${_pkg} found. Using internal sources.") ++ endif() ++ else() ++ message(STATUS "Using ${_pkg} internal sources.") ++ endif(PREFER_EXTERNAL_${_PKG}) ++ # HAVE_${_pkg} will be set to true because even if the library is ++ # not found, we will use the included sources for it ++ set(HAVE_${_PKG} TRUE) ++ list(APPEND LIBS ${_pkg}::${_tgt}) ++ endif(NOT DEACTIVATE_${_PKG}) ++endmacro() ++ ++set(ZLIB_ROOT $ENV{ZLIB_ROOT}) ++use_package(ZLIB ZLIB) ++use_package(LZ4 LZ4) ++use_package(Snappy snappy) ++use_package(Zstd Zstd) + + # create the config.h file + configure_file ("blosc/config.h.in" "blosc/config.h" ) +@@ -316,6 +291,7 @@ endif() + + + # subdirectories ++add_subdirectory(internal-complibs) + add_subdirectory(blosc) + + if(BUILD_TESTS) +@@ -328,7 +304,6 @@ if(BUILD_BENCHMARKS) + add_subdirectory(bench) + endif(BUILD_BENCHMARKS) + +- + # uninstall target + if (BLOSC_INSTALL) + configure_file( +@@ -338,10 +313,38 @@ if (BLOSC_INSTALL) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/blosc.pc" + DESTINATION lib/pkgconfig COMPONENT DEV) + ++ configure_file( ++ "${CMAKE_CURRENT_SOURCE_DIR}/cmake_config.cmake.in" ++ "${CMAKE_CURRENT_BINARY_DIR}/cmakeexports/BloscConfig.cmake" ++ @ONLY) ++ + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) ++ ++ include(CMakePackageConfigHelpers) ++ write_basic_package_version_file( ++ "${CMAKE_CURRENT_BINARY_DIR}/cmakeexports/BloscConfigVersion.cmake" ++ VERSION ${BLOSC_VERSION_MAJOR}.${BLOSC_VERSION_MINOR}.${BLOSC_VERSION_PATCH} ++ COMPATIBILITY AnyNewerVersion ++ ) ++ ++ export(EXPORT BloscTargets ++ FILE "${CMAKE_CURRENT_BINARY_DIR}/cmakeexports/BloscTargets.cmake" ++ NAMESPACE Blosc::) ++ ++ install(EXPORT BloscTargets ++ FILE BloscTargets.cmake ++ NAMESPACE Blosc:: ++ DESTINATION lib/cmake/Blosc ++ EXPORT_LINK_INTERFACE_LIBRARIES) ++ ++ install(FILES ++ "${CMAKE_CURRENT_BINARY_DIR}/cmakeexports/BloscConfig.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/cmakeexports/BloscConfigVersion.cmake" ++ DESTINATION lib/cmake/Blosc COMPONENT DEV) ++ + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + endif() +diff --git a/blosc/CMakeLists.txt b/blosc/CMakeLists.txt +index 1d1bebe..f554abe 100644 +--- a/blosc/CMakeLists.txt ++++ b/blosc/CMakeLists.txt +@@ -1,52 +1,11 @@ + # a simple way to detect that we are using CMAKE + add_definitions(-DUSING_CMAKE) + +-set(INTERNAL_LIBS ${PROJECT_SOURCE_DIR}/internal-complibs) +- + # Hide symbols by default unless they're specifically exported. + # This makes it easier to keep the set of exported symbols the + # same across all compilers/platforms. + set(CMAKE_C_VISIBILITY_PRESET hidden) + +-# includes +-set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}) +-if(NOT DEACTIVATE_LZ4) +- if (LZ4_FOUND) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${LZ4_INCLUDE_DIR}) +- else(LZ4_FOUND) +- set(LZ4_LOCAL_DIR ${INTERNAL_LIBS}/lz4-1.9.1) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${LZ4_LOCAL_DIR}) +- endif(LZ4_FOUND) +-endif(NOT DEACTIVATE_LZ4) +- +-if(NOT DEACTIVATE_SNAPPY) +- if (SNAPPY_FOUND) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${SNAPPY_INCLUDE_DIR}) +- else(SNAPPY_FOUND) +- set(SNAPPY_LOCAL_DIR ${INTERNAL_LIBS}/snappy-1.1.1) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${SNAPPY_LOCAL_DIR}) +- endif(SNAPPY_FOUND) +-endif(NOT DEACTIVATE_SNAPPY) +- +-if(NOT DEACTIVATE_ZLIB) +- if (ZLIB_FOUND) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR}) +- else(ZLIB_FOUND) +- set(ZLIB_LOCAL_DIR ${INTERNAL_LIBS}/zlib-1.2.8) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${ZLIB_LOCAL_DIR}) +- endif(ZLIB_FOUND) +-endif(NOT DEACTIVATE_ZLIB) +- +-if (NOT DEACTIVATE_ZSTD) +- if (ZSTD_FOUND) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${ZSTD_INCLUDE_DIR}) +- else (ZSTD_FOUND) +- set(ZSTD_LOCAL_DIR ${INTERNAL_LIBS}/zstd-1.4.1) +- set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${ZSTD_LOCAL_DIR} ${ZSTD_LOCAL_DIR}/common) +- endif (ZSTD_FOUND) +-endif (NOT DEACTIVATE_ZSTD) +- +-include_directories(${BLOSC_INCLUDE_DIRS}) + + # library sources + set(SOURCES blosc.c blosclz.c fastcopy.c shuffle-generic.c bitshuffle-generic.c +@@ -73,53 +32,13 @@ if(WIN32) + message(STATUS "using the internal pthread library for win32 systems.") + set(SOURCES ${SOURCES} win32/pthread.c) + else(NOT Threads_FOUND) +- set(LIBS ${LIBS} ${CMAKE_THREAD_LIBS_INIT}) ++ list(APPEND LIBS Threads::Threads) + endif(NOT Threads_FOUND) + else(WIN32) + find_package(Threads REQUIRED) +- set(LIBS ${LIBS} ${CMAKE_THREAD_LIBS_INIT}) ++ list(APPEND LIBS Threads::Threads) + endif(WIN32) + +-if(NOT DEACTIVATE_LZ4) +- if(LZ4_FOUND) +- set(LIBS ${LIBS} ${LZ4_LIBRARY}) +- else(LZ4_FOUND) +- file(GLOB LZ4_FILES ${LZ4_LOCAL_DIR}/*.c) +- set(SOURCES ${SOURCES} ${LZ4_FILES}) +- endif(LZ4_FOUND) +-endif(NOT DEACTIVATE_LZ4) +- +-if(NOT DEACTIVATE_SNAPPY) +- if(SNAPPY_FOUND) +- set(LIBS ${LIBS} ${SNAPPY_LIBRARY}) +- else(SNAPPY_FOUND) +- file(GLOB SNAPPY_FILES ${SNAPPY_LOCAL_DIR}/*.cc) +- set(SOURCES ${SOURCES} ${SNAPPY_FILES}) +- endif(SNAPPY_FOUND) +-endif(NOT DEACTIVATE_SNAPPY) +- +-if(NOT DEACTIVATE_ZLIB) +- if(ZLIB_FOUND) +- set(LIBS ${LIBS} ${ZLIB_LIBRARY}) +- else(ZLIB_FOUND) +- file(GLOB ZLIB_FILES ${ZLIB_LOCAL_DIR}/*.c) +- set(SOURCES ${SOURCES} ${ZLIB_FILES}) +- endif(ZLIB_FOUND) +-endif(NOT DEACTIVATE_ZLIB) +- +-if (NOT DEACTIVATE_ZSTD) +- if (ZSTD_FOUND) +- set(LIBS ${LIBS} ${ZSTD_LIBRARY}) +- else (ZSTD_FOUND) +- file(GLOB ZSTD_FILES +- ${ZSTD_LOCAL_DIR}/common/*.c +- ${ZSTD_LOCAL_DIR}/compress/*.c +- ${ZSTD_LOCAL_DIR}/decompress/*.c) +- set(SOURCES ${SOURCES} ${ZSTD_FILES}) +- endif (ZSTD_FOUND) +-endif (NOT DEACTIVATE_ZSTD) +- +- + # targets + if (BUILD_SHARED) + add_library(blosc_shared SHARED ${SOURCES}) +@@ -191,14 +110,17 @@ if (BUILD_TESTS) + endif() + endif() + ++add_library(blosc INTERFACE) ++ + if (BUILD_SHARED) +- target_link_libraries(blosc_shared ${LIBS}) +- target_include_directories(blosc_shared PUBLIC ${BLOSC_INCLUDE_DIRS}) ++ target_link_libraries(blosc_shared PRIVATE ${LIBS}) ++ target_include_directories(blosc_shared PUBLIC $) ++ target_link_libraries(blosc INTERFACE blosc_shared) + endif() + + if (BUILD_TESTS) +- target_link_libraries(blosc_shared_testing ${LIBS}) +- target_include_directories(blosc_shared_testing PUBLIC ${BLOSC_INCLUDE_DIRS}) ++ target_link_libraries(blosc_shared_testing PRIVATE ${LIBS}) ++ target_include_directories(blosc_shared_testing PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if(BUILD_STATIC) +@@ -207,17 +129,31 @@ if(BUILD_STATIC) + if (MSVC) + set_target_properties(blosc_static PROPERTIES PREFIX lib) + endif() +- target_link_libraries(blosc_static ${LIBS}) +- target_include_directories(blosc_static PUBLIC ${BLOSC_INCLUDE_DIRS}) ++ # With the static library, cmake has to deal with transitive dependencies ++ target_link_libraries(blosc_static PRIVATE ${LIBS}) ++ target_include_directories(blosc_static PUBLIC $) ++ if (NOT BUILD_SHARED) ++ target_link_libraries(blosc INTERFACE blosc_static) ++ endif() + endif(BUILD_STATIC) + ++ + # install + if(BLOSC_INSTALL) + install(FILES blosc.h blosc-export.h DESTINATION include COMPONENT DEV) ++ set(_inst_libs "blosc") + if(BUILD_SHARED) +- install(TARGETS blosc_shared DESTINATION ${lib_dir} COMPONENT LIB) ++ list(APPEND _inst_libs blosc_shared) + endif(BUILD_SHARED) + if(BUILD_STATIC) +- install(TARGETS blosc_static DESTINATION ${lib_dir} COMPONENT DEV) ++ list(APPEND _inst_libs blosc_static) + endif(BUILD_STATIC) ++ ++ install(TARGETS ${_inst_libs} ++ EXPORT BloscTargets ++ LIBRARY DESTINATION ${lib_dir} ++ ARCHIVE DESTINATION ${lib_dir} ++ RUNTIME DESTINATION bin ++ COMPONENT DEV ++ INCLUDES DESTINATION include) + endif(BLOSC_INSTALL) +diff --git a/cmake/FindLZ4.cmake b/cmake/FindLZ4.cmake +index e581a80..05de6ef 100644 +--- a/cmake/FindLZ4.cmake ++++ b/cmake/FindLZ4.cmake +@@ -5,6 +5,10 @@ find_library(LZ4_LIBRARY NAMES lz4) + if (LZ4_INCLUDE_DIR AND LZ4_LIBRARY) + set(LZ4_FOUND TRUE) + message(STATUS "Found LZ4 library: ${LZ4_LIBRARY}") ++ add_library(LZ4::LZ4 UNKNOWN IMPORTED) ++ set_target_properties(LZ4::LZ4 PROPERTIES ++ IMPORTED_LOCATION ${LZ4_LIBRARY} ++ INTERFACE_INCLUDE_DIRECTORIES ${LZ4_INCLUDE_DIR}) + else () + message(STATUS "No LZ4 library found. Using internal sources.") +-endif () ++endif () +\ No newline at end of file +diff --git a/cmake/FindSnappy.cmake b/cmake/FindSnappy.cmake +index 688d4d5..21dbee1 100644 +--- a/cmake/FindSnappy.cmake ++++ b/cmake/FindSnappy.cmake +@@ -3,8 +3,12 @@ find_path(SNAPPY_INCLUDE_DIR snappy-c.h) + find_library(SNAPPY_LIBRARY NAMES snappy) + + if (SNAPPY_INCLUDE_DIR AND SNAPPY_LIBRARY) +- set(SNAPPY_FOUND TRUE) ++ set(Snappy_FOUND TRUE) ++ add_library(Snappy::snappy UNKNOWN IMPORTED) ++ set_target_properties(Snappy::snappy PROPERTIES ++ IMPORTED_LOCATION ${SNAPPY_LIBRARY} ++ INTERFACE_INCLUDE_DIRECTORIES ${SNAPPY_INCLUDE_DIR}) + message(STATUS "Found SNAPPY library: ${SNAPPY_LIBRARY}") + else () + message(STATUS "No snappy found. Using internal sources.") +-endif () ++endif () +\ No newline at end of file +diff --git a/cmake/FindZstd.cmake b/cmake/FindZstd.cmake +index 7db4bb9..cabc2f8 100644 +--- a/cmake/FindZstd.cmake ++++ b/cmake/FindZstd.cmake +@@ -3,8 +3,12 @@ find_path(ZSTD_INCLUDE_DIR zstd.h) + find_library(ZSTD_LIBRARY NAMES zstd) + + if (ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY) +- set(ZSTD_FOUND TRUE) ++ set(Zstd_FOUND TRUE) ++ add_library(Zstd::Zstd UNKNOWN IMPORTED) ++ set_target_properties(Zstd::Zstd PROPERTIES ++ IMPORTED_LOCATION ${ZSTD_LIBRARY} ++ INTERFACE_INCLUDE_DIRECTORIES ${ZSTD_INCLUDE_DIR}) + message(STATUS "Found Zstd library: ${ZSTD_LIBRARY}") + else () + message(STATUS "No Zstd library found. Using internal sources.") +-endif () ++endif () +\ No newline at end of file +diff --git a/cmake_config.cmake.in b/cmake_config.cmake.in +new file mode 100644 +index 0000000..0f6af24 +--- /dev/null ++++ b/cmake_config.cmake.in +@@ -0,0 +1,24 @@ ++include(CMakeFindDependencyMacro) ++ ++include("${CMAKE_CURRENT_LIST_DIR}/BloscTargets.cmake") ++ ++function(_blosc_remap_configs from_Cfg to_Cfg) ++ string(TOUPPER ${from_Cfg} from_CFG) ++ string(TOLOWER ${from_Cfg} from_cfg) ++ ++ if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/BloscTargets-${from_cfg}.cmake) ++ foreach(tgt IN ITEMS blosc_static blosc_shared blosc) ++ if(TARGET Blosc::${tgt}) ++ set_target_properties(Blosc::${tgt} PROPERTIES ++ MAP_IMPORTED_CONFIG_${from_CFG} ${to_Cfg}) ++ endif() ++ endforeach() ++ endif() ++endfunction() ++ ++# MSVC will try to link RelWithDebInfo or MinSizeRel target with debug config ++# if no matching installation is present which would result in link errors. ++if(MSVC) ++ _blosc_remap_configs(RelWithDebInfo Release) ++ _blosc_remap_configs(MinSizeRel Release) ++endif() +diff --git a/internal-complibs/CMakeLists.txt b/internal-complibs/CMakeLists.txt +new file mode 100644 +index 0000000..4586efa +--- /dev/null ++++ b/internal-complibs/CMakeLists.txt +@@ -0,0 +1,35 @@ ++macro(add_lib_target pkg tgt incdir files) ++ string(TOUPPER ${pkg} TGT) ++ if(NOT DEACTIVATE_${TGT} AND NOT ${pkg}_FOUND) ++ add_library(${tgt}_objs OBJECT ${files}) ++ add_library(${tgt} INTERFACE) ++ target_include_directories(${tgt}_objs PRIVATE $) ++ target_include_directories(${tgt} INTERFACE $) ++ #set_target_properties(${tgt} PROPERTIES INTERFACE_SOURCES "$") ++ set_target_properties(${tgt}_objs PROPERTIES POSITION_INDEPENDENT_CODE ON) ++ target_sources(${tgt} INTERFACE "$>") ++ add_library(${pkg}::${tgt} ALIAS ${tgt}) ++ ++ # This creates dummy (empty) interface targets in the exported config. ++ install(TARGETS ${tgt} EXPORT BloscTargets INCLUDES DESTINATION include) ++ endif() ++ unset(TGT) ++endmacro() ++ ++set(ZLIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib-1.2.8) ++file(GLOB ZLIB_FILES ${ZLIB_DIR}/*.c) ++add_lib_target(ZLIB ZLIB ${ZLIB_DIR} "${ZLIB_FILES}") ++ ++set(SNAPPY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/snappy-1.1.1) ++file(GLOB SNAPPY_FILES ${SNAPPY_DIR}/*.cc) ++add_lib_target(Snappy snappy ${SNAPPY_DIR} "${SNAPPY_FILES}") ++ ++set(LZ4_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lz4-1.9.1) ++file(GLOB LZ4_FILES ${LZ4_DIR}/*.c) ++add_lib_target(LZ4 LZ4 ${LZ4_DIR} "${LZ4_FILES}") ++ ++set(ZSTD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zstd-1.4.1) ++file(GLOB ZSTD_FILES ${ZSTD_DIR}/common/*.c ${ZSTD_DIR}/compress/*.c ${ZSTD_DIR}/decompress/*.c) ++add_lib_target(Zstd Zstd ${ZSTD_DIR} "${ZSTD_FILES}") ++target_include_directories(Zstd INTERFACE $) ++target_include_directories(Zstd_objs PRIVATE $) +\ No newline at end of file +-- +2.16.2.windows.1 + diff --git a/deps/deps-linux.cmake b/deps/deps-linux.cmake index 03e8e12d57..f5571d4704 100644 --- a/deps/deps-linux.cmake +++ b/deps/deps-linux.cmake @@ -5,11 +5,11 @@ include("deps-unix-common.cmake") ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" - URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 + URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" + URL_HASH SHA256=882b48708d211a5f48e60b0124cf5863c1534cd544ecd0664bb534a4b5d506e9 BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./bootstrap.sh - --with-libraries=system,filesystem,thread,log,locale,regex + --with-libraries=system,iostreams,filesystem,thread,log,locale,regex "--prefix=${DESTDIR}/usr/local" BUILD_COMMAND ./b2 -j ${NPROC} @@ -123,3 +123,5 @@ ExternalProject_Add(dep_wxwidgets BUILD_COMMAND make "-j${NPROC}" && make -C locale allmo INSTALL_COMMAND make install ) + +add_dependencies(dep_openvdb dep_boost) \ No newline at end of file diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index d22e4a2e2c..fc08c62903 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -6,7 +6,7 @@ set(DEP_WERRORS_SDK "-Werror=partial-availability -Werror=unguarded-availability set(DEP_CMAKE_OPTS "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" "-DCMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT}" - "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}" + "-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEP_OSX_TARGET}" "-DCMAKE_CXX_FLAGS=${DEP_WERRORS_SDK}" "-DCMAKE_C_FLAGS=${DEP_WERRORS_SDK}" ) @@ -14,28 +14,27 @@ set(DEP_CMAKE_OPTS include("deps-unix-common.cmake") -set(DEP_BOOST_OSX_TARGET "") -if (CMAKE_OSX_DEPLOYMENT_TARGET) - set(DEP_BOOST_OSX_TARGET "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") -endif () - ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" - URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 + URL "https://dl.bintray.com/boostorg/release/1.71.0/source/boost_1_71_0.tar.gz" + URL_HASH SHA256=96b34f7468f26a141f6020efb813f1a2f3dfb9797ecf76a7d7cbd843cc95f5bd BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./bootstrap.sh - --with-libraries=system,filesystem,thread,log,locale,regex + --with-toolset=clang + --with-libraries=system,iostreams,filesystem,thread,log,locale,regex "--prefix=${DESTDIR}/usr/local" BUILD_COMMAND ./b2 -j ${NPROC} --reconfigure + toolset=clang link=static variant=release threading=multi boost.locale.icu=off - "cflags=-fPIC ${DEP_BOOST_OSX_TARGET}" - "cxxflags=-fPIC ${DEP_BOOST_OSX_TARGET}" + "cflags=-fPIC -mmacosx-version-min=${DEP_OSX_TARGET}" + "cxxflags=-fPIC -mmacosx-version-min=${DEP_OSX_TARGET}" + "mflags=-fPIC -mmacosx-version-min=${DEP_OSX_TARGET}" + "mmflags=-fPIC -mmacosx-version-min=${DEP_OSX_TARGET}" install INSTALL_COMMAND "" # b2 does that already ) @@ -114,3 +113,5 @@ ExternalProject_Add(dep_wxwidgets BUILD_COMMAND make "-j${NPROC}" && PATH=/usr/local/opt/gettext/bin/:$ENV{PATH} make -C locale allmo INSTALL_COMMAND make install ) + +add_dependencies(dep_openvdb dep_boost) \ No newline at end of file diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index 6e559d05a3..eae319efc6 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -7,6 +7,8 @@ else () set(TBB_MINGW_WORKAROUND "") endif () +find_package(ZLIB REQUIRED) + ExternalProject_Add(dep_tbb EXCLUDE_FROM_ALL 1 URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" @@ -53,40 +55,67 @@ find_package(Git REQUIRED) ExternalProject_Add(dep_qhull EXCLUDE_FROM_ALL 1 - URL "https://github.com/qhull/qhull/archive/v7.2.1.tar.gz" - URL_HASH SHA256=6fc251e0b75467e00943bfb7191e986fce0e1f8f6f0251f9c6ce5a843821ea78 + URL "https://github.com/qhull/qhull/archive/v7.3.2.tar.gz" + URL_HASH SHA256=619c8a954880d545194bc03359404ef36a1abd2dde03678089459757fd790cb0 CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} - PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch + UPDATE_COMMAND "" + PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch ) -ExternalProject_Add(dep_libigl +ExternalProject_Add(dep_blosc EXCLUDE_FROM_ALL 1 - URL "https://github.com/libigl/libigl/archive/v2.0.0.tar.gz" - URL_HASH SHA256=42518e6b106c7209c73435fd260ed5d34edeb254852495b4c95dce2d95401328 + GIT_REPOSITORY https://github.com/Blosc/c-blosc.git + GIT_TAG e63775855294b50820ef44d1b157f4de1cc38d3e #v1.17.0 + DEPENDS CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local - -DLIBIGL_BUILD_PYTHON=OFF - -DLIBIGL_BUILD_TESTS=OFF - -DLIBIGL_BUILD_TUTORIALS=OFF - -DLIBIGL_USE_STATIC_LIBRARY=OFF #${DEP_BUILD_IGL_STATIC} - -DLIBIGL_WITHOUT_COPYLEFT=OFF - -DLIBIGL_WITH_CGAL=OFF - -DLIBIGL_WITH_COMISO=OFF - -DLIBIGL_WITH_CORK=OFF - -DLIBIGL_WITH_EMBREE=OFF - -DLIBIGL_WITH_MATLAB=OFF - -DLIBIGL_WITH_MOSEK=OFF - -DLIBIGL_WITH_OPENGL=OFF - -DLIBIGL_WITH_OPENGL_GLFW=OFF - -DLIBIGL_WITH_OPENGL_GLFW_IMGUI=OFF - -DLIBIGL_WITH_PNG=OFF - -DLIBIGL_WITH_PYTHON=OFF - -DLIBIGL_WITH_TETGEN=OFF - -DLIBIGL_WITH_TRIANGLE=OFF - -DLIBIGL_WITH_XML=OFF - PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/igl-fixes.patch + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + -DBUILD_SHARED=OFF + -DBUILD_STATIC=ON + -DBUILD_TESTS=OFF + -DBUILD_BENCHMARKS=OFF + -DPREFER_EXTERNAL_ZLIB=ON + UPDATE_COMMAND "" + PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/blosc-mods.patch ) +ExternalProject_Add(dep_openexr + EXCLUDE_FROM_ALL 1 + GIT_REPOSITORY https://github.com/openexr/openexr.git + GIT_TAG eae0e337c9f5117e78114fd05f7a415819df413a #v2.4.0 + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DBUILD_TESTING=OFF + -DPYILMBASE_ENABLE:BOOL=OFF + -DOPENEXR_VIEWERS_ENABLE:BOOL=OFF + -DOPENEXR_BUILD_UTILS:BOOL=OFF + UPDATE_COMMAND "" +) + +ExternalProject_Add(dep_openvdb + EXCLUDE_FROM_ALL 1 + GIT_REPOSITORY https://github.com/AcademySoftwareFoundation/openvdb.git + GIT_TAG aebaf8d95be5e57fd33949281ec357db4a576c2e #v6.2.1 + DEPENDS dep_blosc dep_openexr dep_tbb + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DCMAKE_DEBUG_POSTFIX=d + -DCMAKE_PREFIX_PATH=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DOPENVDB_BUILD_PYTHON_MODULE=OFF + -DUSE_BLOSC=ON + -DOPENVDB_CORE_SHARED=OFF + -DOPENVDB_CORE_STATIC=ON + -DTBB_STATIC=ON + -DOPENVDB_BUILD_VDB_PRINT=ON + UPDATE_COMMAND "" + PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/openvdb-mods.patch +) \ No newline at end of file diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 85013fbddd..514a90a9ec 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -43,6 +43,18 @@ else () set(DEP_BOOST_DEBUG "") endif () +macro(add_debug_dep _dep) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(${_dep} BINARY_DIR) + ExternalProject_Add_Step(${_dep} build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () +endmacro() + ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" @@ -52,6 +64,7 @@ ExternalProject_Add(dep_boost BUILD_COMMAND b2.exe -j "${NPROC}" --with-system + --with-iostreams --with-filesystem --with-thread --with-log @@ -68,7 +81,6 @@ ExternalProject_Add(dep_boost INSTALL_COMMAND "" # b2 does that already ) - ExternalProject_Add(dep_tbb EXCLUDE_FROM_ALL 1 URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" @@ -83,41 +95,25 @@ ExternalProject_Add(dep_tbb BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj INSTALL_COMMAND "" ) -if (${DEP_DEBUG}) - ExternalProject_Get_Property(dep_tbb BINARY_DIR) - ExternalProject_Add_Step(dep_tbb build_debug - DEPENDEES build - DEPENDERS install - COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj - WORKING_DIRECTORY "${BINARY_DIR}" - ) -endif () +add_debug_dep(dep_tbb) -ExternalProject_Add(dep_gtest - EXCLUDE_FROM_ALL 1 - URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" - URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c - CMAKE_GENERATOR "${DEP_MSVC_GEN}" - CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" - CMAKE_ARGS - -DBUILD_GMOCK=OFF - -Dgtest_force_shared_crt=ON - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" - BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj - INSTALL_COMMAND "" -) -if (${DEP_DEBUG}) - ExternalProject_Get_Property(dep_gtest BINARY_DIR) - ExternalProject_Add_Step(dep_gtest build_debug - DEPENDEES build - DEPENDERS install - COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj - WORKING_DIRECTORY "${BINARY_DIR}" - ) -endif () +# ExternalProject_Add(dep_gtest +# EXCLUDE_FROM_ALL 1 +# URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" +# URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c +# CMAKE_GENERATOR "${DEP_MSVC_GEN}" +# CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" +# CMAKE_ARGS +# -DBUILD_GMOCK=OFF +# -Dgtest_force_shared_crt=ON +# -DCMAKE_POSITION_INDEPENDENT_CODE=ON +# "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" +# BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj +# INSTALL_COMMAND "" +# ) +# add_debug_dep(dep_gtest) ExternalProject_Add(dep_cereal EXCLUDE_FROM_ALL 1 @@ -132,7 +128,6 @@ ExternalProject_Add(dep_cereal INSTALL_COMMAND "" ) - ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" @@ -151,16 +146,8 @@ ExternalProject_Add(dep_nlopt BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj INSTALL_COMMAND "" ) -if (${DEP_DEBUG}) - ExternalProject_Get_Property(dep_nlopt BINARY_DIR) - ExternalProject_Add_Step(dep_nlopt build_debug - DEPENDEES build - DEPENDERS install - COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj - WORKING_DIRECTORY "${BINARY_DIR}" - ) -endif () +add_debug_dep(dep_nlopt) ExternalProject_Add(dep_zlib EXCLUDE_FROM_ALL 1 @@ -176,15 +163,9 @@ ExternalProject_Add(dep_zlib BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj INSTALL_COMMAND "" ) -if (${DEP_DEBUG}) - ExternalProject_Get_Property(dep_zlib BINARY_DIR) - ExternalProject_Add_Step(dep_zlib build_debug - DEPENDEES build - DEPENDERS install - COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj - WORKING_DIRECTORY "${BINARY_DIR}" - ) -endif () + +add_debug_dep(dep_zlib) + # The following steps are unfortunately needed to remove the _static suffix on libraries ExternalProject_Add_Step(dep_zlib fix_static DEPENDEES install @@ -199,7 +180,6 @@ if (${DEP_DEBUG}) ) endif () - if (${DEPS_BITS} EQUAL 32) set(DEP_LIBCURL_TARGET "x86") else () @@ -238,29 +218,21 @@ find_package(Git REQUIRED) ExternalProject_Add(dep_qhull EXCLUDE_FROM_ALL 1 - URL "https://github.com/qhull/qhull/archive/v7.2.1.tar.gz" - URL_HASH SHA256=6fc251e0b75467e00943bfb7191e986fce0e1f8f6f0251f9c6ce5a843821ea78 + URL "https://github.com/qhull/qhull/archive/v7.3.2.tar.gz" + URL_HASH SHA256=619c8a954880d545194bc03359404ef36a1abd2dde03678089459757fd790cb0 CMAKE_GENERATOR "${DEP_MSVC_GEN}" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_DEBUG_POSTFIX=d - PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch + UPDATE_COMMAND "" + PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj INSTALL_COMMAND "" ) -if (${DEP_DEBUG}) - ExternalProject_Get_Property(dep_qhull BINARY_DIR) - ExternalProject_Add_Step(dep_qhull build_debug - DEPENDEES build - DEPENDERS install - COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj - WORKING_DIRECTORY "${BINARY_DIR}" - ) -endif () - +add_debug_dep(dep_qhull) if (${DEPS_BITS} EQUAL 32) set(DEP_WXWIDGETS_TARGET "") @@ -272,49 +244,6 @@ endif () find_package(Git REQUIRED) -ExternalProject_Add(dep_libigl - EXCLUDE_FROM_ALL 1 - URL "https://github.com/libigl/libigl/archive/v2.0.0.tar.gz" - URL_HASH SHA256=42518e6b106c7209c73435fd260ed5d34edeb254852495b4c95dce2d95401328 - CMAKE_GENERATOR "${DEP_MSVC_GEN}" - CMAKE_ARGS - -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local - -DLIBIGL_BUILD_PYTHON=OFF - -DLIBIGL_BUILD_TESTS=OFF - -DLIBIGL_BUILD_TUTORIALS=OFF - -DLIBIGL_USE_STATIC_LIBRARY=OFF #${DEP_BUILD_IGL_STATIC} - -DLIBIGL_WITHOUT_COPYLEFT=OFF - -DLIBIGL_WITH_CGAL=OFF - -DLIBIGL_WITH_COMISO=OFF - -DLIBIGL_WITH_CORK=OFF - -DLIBIGL_WITH_EMBREE=OFF - -DLIBIGL_WITH_MATLAB=OFF - -DLIBIGL_WITH_MOSEK=OFF - -DLIBIGL_WITH_OPENGL=OFF - -DLIBIGL_WITH_OPENGL_GLFW=OFF - -DLIBIGL_WITH_OPENGL_GLFW_IMGUI=OFF - -DLIBIGL_WITH_PNG=OFF - -DLIBIGL_WITH_PYTHON=OFF - -DLIBIGL_WITH_TETGEN=OFF - -DLIBIGL_WITH_TRIANGLE=OFF - -DLIBIGL_WITH_XML=OFF - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DCMAKE_DEBUG_POSTFIX=d - PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/igl-fixes.patch - BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj - INSTALL_COMMAND "" -) - -if (${DEP_DEBUG}) - ExternalProject_Get_Property(dep_libigl BINARY_DIR) - ExternalProject_Add_Step(dep_libigl build_debug - DEPENDEES build - DEPENDERS install - COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj - WORKING_DIRECTORY "${BINARY_DIR}" - ) -endif () - ExternalProject_Add(dep_wxwidgets EXCLUDE_FROM_ALL 1 GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" @@ -337,3 +266,92 @@ if (${DEP_DEBUG}) WORKING_DIRECTORY "${SOURCE_DIR}" ) endif () + +ExternalProject_Add(dep_blosc + EXCLUDE_FROM_ALL 1 + #URL https://github.com/Blosc/c-blosc/archive/v1.17.0.zip + #URL_HASH SHA256=7463a1df566704f212263312717ab2c36b45d45cba6cd0dccebf91b2cc4b4da9 + GIT_REPOSITORY https://github.com/Blosc/c-blosc.git + GIT_TAG e63775855294b50820ef44d1b157f4de1cc38d3e #v1.17.0 + DEPENDS dep_zlib + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + -DBUILD_SHARED=OFF + -DBUILD_STATIC=ON + -DBUILD_TESTS=OFF + -DBUILD_BENCHMARKS=OFF + -DPREFER_EXTERNAL_ZLIB=ON + -DBLOSC_IS_SUBPROJECT:BOOL=ON + -DBLOSC_INSTALL:BOOL=ON + UPDATE_COMMAND "" + PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/blosc-mods.patch + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + +add_debug_dep(dep_blosc) + +ExternalProject_Add(dep_openexr + EXCLUDE_FROM_ALL 1 + GIT_REPOSITORY https://github.com/openexr/openexr.git + GIT_TAG eae0e337c9f5117e78114fd05f7a415819df413a #v2.4.0 + DEPENDS dep_zlib + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DBUILD_TESTING=OFF + -DPYILMBASE_ENABLE:BOOL=OFF + -DOPENEXR_VIEWERS_ENABLE:BOOL=OFF + -DOPENEXR_BUILD_UTILS:BOOL=OFF + UPDATE_COMMAND "" + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + +add_debug_dep(dep_openexr) + +ExternalProject_Add(dep_openvdb + EXCLUDE_FROM_ALL 1 + #URL https://github.com/AcademySoftwareFoundation/openvdb/archive/v6.2.1.zip + #URL_HASH SHA256=dc337399dce8e1c9f21f20e97b1ce7e4933cb0a63bb3b8b734d8fcc464aa0c48 + GIT_REPOSITORY https://github.com/AcademySoftwareFoundation/openvdb.git + GIT_TAG aebaf8d95be5e57fd33949281ec357db4a576c2e #v6.2.1 + DEPENDS dep_blosc dep_openexr #dep_tbb dep_boost + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DCMAKE_DEBUG_POSTFIX=d + -DCMAKE_PREFIX_PATH=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DOPENVDB_BUILD_PYTHON_MODULE=OFF + -DUSE_BLOSC=ON + -DOPENVDB_CORE_SHARED=OFF + -DOPENVDB_CORE_STATIC=ON + -DTBB_STATIC=ON + -DOPENVDB_BUILD_VDB_PRINT=ON + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + UPDATE_COMMAND "" + PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/openvdb-mods.patch + INSTALL_COMMAND "" +) + +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_openvdb BINARY_DIR) + ExternalProject_Add_Step(dep_openvdb build_debug + DEPENDEES build + DEPENDERS install + COMMAND ${CMAKE_COMMAND} ../dep_openvdb -DOPENVDB_BUILD_VDB_PRINT=OFF + COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () \ No newline at end of file diff --git a/deps/igl-fixes.patch b/deps/igl-fixes.patch deleted file mode 100644 index b0ff9205d0..0000000000 --- a/deps/igl-fixes.patch +++ /dev/null @@ -1,128 +0,0 @@ -diff --git a/cmake/libigl-config.cmake.in b/cmake/libigl-config.cmake.in -index 317c745c..f9808e1e 100644 ---- a/cmake/libigl-config.cmake.in -+++ b/cmake/libigl-config.cmake.in -@@ -2,28 +2,28 @@ - - include(${CMAKE_CURRENT_LIST_DIR}/libigl-export.cmake) - --if (TARGET igl::core) -- if (NOT TARGET Eigen3::Eigen) -- find_package(Eigen3 QUIET) -- if (NOT Eigen3_FOUND) -- # try with PkgCOnfig -- find_package(PkgConfig REQUIRED) -- pkg_check_modules(Eigen3 QUIET IMPORTED_TARGET eigen3) -- endif() -- -- if (NOT Eigen3_FOUND) -- message(FATAL_ERROR "Could not find required dependency Eigen3") -- set(libigl_core_FOUND FALSE) -- else() -- target_link_libraries(igl::core INTERFACE PkgConfig::Eigen3) -- set(libigl_core_FOUND TRUE) -- endif() -- else() -- target_link_libraries(igl::core INTERFACE Eigen3::Eigen) -- set(libigl_core_FOUND TRUE) -- endif() -- --endif() -+# if (TARGET igl::core) -+# if (NOT TARGET Eigen3::Eigen) -+# find_package(Eigen3 QUIET) -+# if (NOT Eigen3_FOUND) -+# # try with PkgCOnfig -+# find_package(PkgConfig REQUIRED) -+# pkg_check_modules(Eigen3 QUIET IMPORTED_TARGET eigen3) -+# endif() -+# -+# if (NOT Eigen3_FOUND) -+# message(FATAL_ERROR "Could not find required dependency Eigen3") -+# set(libigl_core_FOUND FALSE) -+# else() -+# target_link_libraries(igl::core INTERFACE PkgConfig::Eigen3) -+# set(libigl_core_FOUND TRUE) -+# endif() -+# else() -+# target_link_libraries(igl::core INTERFACE Eigen3::Eigen) -+# set(libigl_core_FOUND TRUE) -+# endif() -+# -+# endif() - - check_required_components(libigl) - -diff --git a/cmake/libigl.cmake b/cmake/libigl.cmake -index 4b11007a..47e6c395 100644 ---- a/cmake/libigl.cmake -+++ b/cmake/libigl.cmake -@@ -445,6 +445,7 @@ function(install_dir_files dir_name) - if(NOT LIBIGL_USE_STATIC_LIBRARY) - file(GLOB public_sources - ${CMAKE_CURRENT_SOURCE_DIR}/include/igl${subpath}/*.cpp -+ ${CMAKE_CURRENT_SOURCE_DIR}/include/igl${subpath}/*.c - ) - endif() - list(APPEND files_to_install ${public_sources}) -diff --git a/include/igl/AABB.cpp b/include/igl/AABB.cpp -index 09537335..92e90cb7 100644 ---- a/include/igl/AABB.cpp -+++ b/include/igl/AABB.cpp -@@ -1071,5 +1071,11 @@ template void igl::AABB, 3>::init, 2>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); - template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, int&, Eigen::PlainObjectBase >&) const; -+template float igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::squared_distance const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const; - template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::Hit&) const; -+template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector&) const; -+ -+template void igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::init const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&); -+ -+template bool igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::intersect_ray const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector >&) const; - #endif -diff --git a/include/igl/barycenter.cpp b/include/igl/barycenter.cpp -index 065f82aa..ec2d96cd 100644 ---- a/include/igl/barycenter.cpp -+++ b/include/igl/barycenter.cpp -@@ -54,4 +54,6 @@ template void igl::barycenter, Eigen::M - template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); - template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); - template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); -+ -+template void igl::barycenter const, 0, Eigen::Stride<0, 0> >, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Matrix >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::PlainObjectBase >&); - #endif -diff --git a/include/igl/point_simplex_squared_distance.cpp b/include/igl/point_simplex_squared_distance.cpp -index 2b98bd28..c66d9ae1 100644 ---- a/include/igl/point_simplex_squared_distance.cpp -+++ b/include/igl/point_simplex_squared_distance.cpp -@@ -178,4 +178,6 @@ template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); - template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); - template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); -+ -+template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Map const, 0, Eigen::Stride<0, 0> >, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Map const, 0, Eigen::Stride<0, 0> >::Index, float&, Eigen::MatrixBase >&); - #endif -diff --git a/include/igl/ray_box_intersect.cpp b/include/igl/ray_box_intersect.cpp -index 4a88b89e..b547f8f8 100644 ---- a/include/igl/ray_box_intersect.cpp -+++ b/include/igl/ray_box_intersect.cpp -@@ -147,4 +147,6 @@ IGL_INLINE bool igl::ray_box_intersect( - #ifdef IGL_STATIC_LIBRARY - // Explicit template instantiation - template bool igl::ray_box_intersect, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, double const&, double const&, double&, double&); -+ -+template bool igl::ray_box_intersect, Eigen::Matrix, float>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, float const&, float const&, float&, float&); - #endif -diff --git a/include/igl/ray_mesh_intersect.cpp b/include/igl/ray_mesh_intersect.cpp -index 9a70a22b..4233e722 100644 ---- a/include/igl/ray_mesh_intersect.cpp -+++ b/include/igl/ray_mesh_intersect.cpp -@@ -83,4 +83,7 @@ IGL_INLINE bool igl::ray_mesh_intersect( - template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >&); - template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::Hit&); - template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, igl::Hit&); -+template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, std::vector >&); -+ -+template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Block const, 0, Eigen::Stride<0, 0> > const, 1, -1, true> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > const, 1, -1, true> > const&, std::vector >&); - #endif diff --git a/deps/openvdb-mods.patch b/deps/openvdb-mods.patch new file mode 100644 index 0000000000..60687b8d17 --- /dev/null +++ b/deps/openvdb-mods.patch @@ -0,0 +1,1782 @@ +From e48f4a835fe7cb391f9f90945472bd367fb4c4f1 Mon Sep 17 00:00:00 2001 +From: tamasmeszaros +Date: Wed, 16 Oct 2019 17:42:50 +0200 +Subject: [PATCH] Build fixes for PrusaSlicer integration + +--- + CMakeLists.txt | 3 - + cmake/FindBlosc.cmake | 218 --------------- + cmake/FindCppUnit.cmake | 4 +- + cmake/FindIlmBase.cmake | 337 ---------------------- + cmake/FindOpenEXR.cmake | 329 ---------------------- + cmake/FindOpenVDB.cmake | 19 +- + cmake/FindTBB.cmake | 605 ++++++++++++++++++++-------------------- + openvdb/CMakeLists.txt | 13 +- + openvdb/Grid.cc | 3 + + openvdb/PlatformConfig.h | 9 +- + openvdb/cmd/CMakeLists.txt | 4 +- + openvdb/unittest/CMakeLists.txt | 3 +- + openvdb/unittest/TestFile.cc | 2 +- + 13 files changed, 336 insertions(+), 1213 deletions(-) + delete mode 100644 cmake/FindBlosc.cmake + delete mode 100644 cmake/FindIlmBase.cmake + delete mode 100644 cmake/FindOpenEXR.cmake + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 580b353..6d364c1 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -267,12 +267,9 @@ endif() + + if(OPENVDB_INSTALL_CMAKE_MODULES) + set(OPENVDB_CMAKE_MODULES +- cmake/FindBlosc.cmake + cmake/FindCppUnit.cmake + cmake/FindJemalloc.cmake +- cmake/FindIlmBase.cmake + cmake/FindLog4cplus.cmake +- cmake/FindOpenEXR.cmake + cmake/FindOpenVDB.cmake + cmake/FindTBB.cmake + cmake/OpenVDBGLFW3Setup.cmake +diff --git a/cmake/FindBlosc.cmake b/cmake/FindBlosc.cmake +deleted file mode 100644 +index 5aacfdd..0000000 +--- a/cmake/FindBlosc.cmake ++++ /dev/null +@@ -1,218 +0,0 @@ +-# Copyright (c) DreamWorks Animation LLC +-# +-# All rights reserved. This software is distributed under the +-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +-# +-# Redistributions of source code must retain the above copyright +-# and license notice and the following restrictions and disclaimer. +-# +-# * Neither the name of DreamWorks Animation nor the names of +-# its contributors may be used to endorse or promote products derived +-# from this software without specific prior written permission. +-# +-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +-# +-#[=======================================================================[.rst: +- +-FindBlosc +---------- +- +-Find Blosc include dirs and libraries +- +-Use this module by invoking find_package with the form:: +- +- find_package(Blosc +- [version] [EXACT] # Minimum or EXACT version e.g. 1.5.0 +- [REQUIRED] # Fail with error if Blosc is not found +- ) +- +-IMPORTED Targets +-^^^^^^^^^^^^^^^^ +- +-``Blosc::blosc`` +- This module defines IMPORTED target Blosc::Blosc, if Blosc has been found. +- +-Result Variables +-^^^^^^^^^^^^^^^^ +- +-This will define the following variables: +- +-``Blosc_FOUND`` +- True if the system has the Blosc library. +-``Blosc_VERSION`` +- The version of the Blosc library which was found. +-``Blosc_INCLUDE_DIRS`` +- Include directories needed to use Blosc. +-``Blosc_LIBRARIES`` +- Libraries needed to link to Blosc. +-``Blosc_LIBRARY_DIRS`` +- Blosc library directories. +- +-Cache Variables +-^^^^^^^^^^^^^^^ +- +-The following cache variables may also be set: +- +-``Blosc_INCLUDE_DIR`` +- The directory containing ``blosc.h``. +-``Blosc_LIBRARY`` +- The path to the Blosc library. +- +-Hints +-^^^^^ +- +-Instead of explicitly setting the cache variables, the following variables +-may be provided to tell this module where to look. +- +-``BLOSC_ROOT`` +- Preferred installation prefix. +-``BLOSC_INCLUDEDIR`` +- Preferred include directory e.g. /include +-``BLOSC_LIBRARYDIR`` +- Preferred library directory e.g. /lib +-``SYSTEM_LIBRARY_PATHS`` +- Paths appended to all include and lib searches. +- +-#]=======================================================================] +- +-mark_as_advanced( +- Blosc_INCLUDE_DIR +- Blosc_LIBRARY +-) +- +-# Append BLOSC_ROOT or $ENV{BLOSC_ROOT} if set (prioritize the direct cmake var) +-set(_BLOSC_ROOT_SEARCH_DIR "") +- +-if(BLOSC_ROOT) +- list(APPEND _BLOSC_ROOT_SEARCH_DIR ${BLOSC_ROOT}) +-else() +- set(_ENV_BLOSC_ROOT $ENV{BLOSC_ROOT}) +- if(_ENV_BLOSC_ROOT) +- list(APPEND _BLOSC_ROOT_SEARCH_DIR ${_ENV_BLOSC_ROOT}) +- endif() +-endif() +- +-# Additionally try and use pkconfig to find blosc +- +-find_package(PkgConfig) +-pkg_check_modules(PC_Blosc QUIET blosc) +- +-# ------------------------------------------------------------------------ +-# Search for blosc include DIR +-# ------------------------------------------------------------------------ +- +-set(_BLOSC_INCLUDE_SEARCH_DIRS "") +-list(APPEND _BLOSC_INCLUDE_SEARCH_DIRS +- ${BLOSC_INCLUDEDIR} +- ${_BLOSC_ROOT_SEARCH_DIR} +- ${PC_Blosc_INCLUDE_DIRS} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Look for a standard blosc header file. +-find_path(Blosc_INCLUDE_DIR blosc.h +- NO_DEFAULT_PATH +- PATHS ${_BLOSC_INCLUDE_SEARCH_DIRS} +- PATH_SUFFIXES include +-) +- +-if(EXISTS "${Blosc_INCLUDE_DIR}/blosc.h") +- file(STRINGS "${Blosc_INCLUDE_DIR}/blosc.h" +- _blosc_version_major_string REGEX "#define BLOSC_VERSION_MAJOR +[0-9]+ " +- ) +- string(REGEX REPLACE "#define BLOSC_VERSION_MAJOR +([0-9]+).*$" "\\1" +- _blosc_version_major_string "${_blosc_version_major_string}" +- ) +- string(STRIP "${_blosc_version_major_string}" Blosc_VERSION_MAJOR) +- +- file(STRINGS "${Blosc_INCLUDE_DIR}/blosc.h" +- _blosc_version_minor_string REGEX "#define BLOSC_VERSION_MINOR +[0-9]+ " +- ) +- string(REGEX REPLACE "#define BLOSC_VERSION_MINOR +([0-9]+).*$" "\\1" +- _blosc_version_minor_string "${_blosc_version_minor_string}" +- ) +- string(STRIP "${_blosc_version_minor_string}" Blosc_VERSION_MINOR) +- +- unset(_blosc_version_major_string) +- unset(_blosc_version_minor_string) +- +- set(Blosc_VERSION ${Blosc_VERSION_MAJOR}.${Blosc_VERSION_MINOR}) +-endif() +- +-# ------------------------------------------------------------------------ +-# Search for blosc lib DIR +-# ------------------------------------------------------------------------ +- +-set(_BLOSC_LIBRARYDIR_SEARCH_DIRS "") +-list(APPEND _BLOSC_LIBRARYDIR_SEARCH_DIRS +- ${BLOSC_LIBRARYDIR} +- ${_BLOSC_ROOT_SEARCH_DIR} +- ${PC_Blosc_LIBRARY_DIRS} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Static library setup +-if(UNIX AND BLOSC_USE_STATIC_LIBS) +- set(_BLOSC_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +-endif() +- +-set(BLOSC_PATH_SUFFIXES +- lib64 +- lib +-) +- +-find_library(Blosc_LIBRARY blosc +- NO_DEFAULT_PATH +- PATHS ${_BLOSC_LIBRARYDIR_SEARCH_DIRS} +- PATH_SUFFIXES ${BLOSC_PATH_SUFFIXES} +-) +- +-if(UNIX AND BLOSC_USE_STATIC_LIBS) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_BLOSC_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +- unset(_BLOSC_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) +-endif() +- +-# ------------------------------------------------------------------------ +-# Cache and set Blosc_FOUND +-# ------------------------------------------------------------------------ +- +-include(FindPackageHandleStandardArgs) +-find_package_handle_standard_args(Blosc +- FOUND_VAR Blosc_FOUND +- REQUIRED_VARS +- Blosc_LIBRARY +- Blosc_INCLUDE_DIR +- VERSION_VAR Blosc_VERSION +-) +- +-if(Blosc_FOUND) +- set(Blosc_LIBRARIES ${Blosc_LIBRARY}) +- set(Blosc_INCLUDE_DIRS ${Blosc_INCLUDE_DIR}) +- set(Blosc_DEFINITIONS ${PC_Blosc_CFLAGS_OTHER}) +- +- get_filename_component(Blosc_LIBRARY_DIRS ${Blosc_LIBRARY} DIRECTORY) +- +- if(NOT TARGET Blosc::blosc) +- add_library(Blosc::blosc UNKNOWN IMPORTED) +- set_target_properties(Blosc::blosc PROPERTIES +- IMPORTED_LOCATION "${Blosc_LIBRARIES}" +- INTERFACE_COMPILE_DEFINITIONS "${Blosc_DEFINITIONS}" +- INTERFACE_INCLUDE_DIRECTORIES "${Blosc_INCLUDE_DIRS}" +- ) +- endif() +-elseif(Blosc_FIND_REQUIRED) +- message(FATAL_ERROR "Unable to find Blosc") +-endif() +diff --git a/cmake/FindCppUnit.cmake b/cmake/FindCppUnit.cmake +index e2beb93..a891624 100644 +--- a/cmake/FindCppUnit.cmake ++++ b/cmake/FindCppUnit.cmake +@@ -125,7 +125,7 @@ list(APPEND _CPPUNIT_INCLUDE_SEARCH_DIRS + + # Look for a standard cppunit header file. + find_path(CppUnit_INCLUDE_DIR cppunit/Portability.h +- NO_DEFAULT_PATH ++ # NO_DEFAULT_PATH + PATHS ${_CPPUNIT_INCLUDE_SEARCH_DIRS} + PATH_SUFFIXES include + ) +@@ -177,7 +177,7 @@ set(CPPUNIT_PATH_SUFFIXES + ) + + find_library(CppUnit_LIBRARY cppunit +- NO_DEFAULT_PATH ++ # NO_DEFAULT_PATH + PATHS ${_CPPUNIT_LIBRARYDIR_SEARCH_DIRS} + PATH_SUFFIXES ${CPPUNIT_PATH_SUFFIXES} + ) +diff --git a/cmake/FindIlmBase.cmake b/cmake/FindIlmBase.cmake +deleted file mode 100644 +index 9dbc252..0000000 +--- a/cmake/FindIlmBase.cmake ++++ /dev/null +@@ -1,337 +0,0 @@ +-# Copyright (c) DreamWorks Animation LLC +-# +-# All rights reserved. This software is distributed under the +-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +-# +-# Redistributions of source code must retain the above copyright +-# and license notice and the following restrictions and disclaimer. +-# +-# * Neither the name of DreamWorks Animation nor the names of +-# its contributors may be used to endorse or promote products derived +-# from this software without specific prior written permission. +-# +-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +-# +-#[=======================================================================[.rst: +- +-FindIlmBase +------------ +- +-Find IlmBase include dirs and libraries +- +-Use this module by invoking find_package with the form:: +- +- find_package(IlmBase +- [version] [EXACT] # Minimum or EXACT version +- [REQUIRED] # Fail with error if IlmBase is not found +- [COMPONENTS ...] # IlmBase libraries by their canonical name +- # e.g. "Half" for "libHalf" +- ) +- +-IMPORTED Targets +-^^^^^^^^^^^^^^^^ +- +-``IlmBase::Half`` +- The Half library target. +-``IlmBase::Iex`` +- The Iex library target. +-``IlmBase::IexMath`` +- The IexMath library target. +-``IlmBase::IlmThread`` +- The IlmThread library target. +-``IlmBase::Imath`` +- The Imath library target. +- +-Result Variables +-^^^^^^^^^^^^^^^^ +- +-This will define the following variables: +- +-``IlmBase_FOUND`` +- True if the system has the IlmBase library. +-``IlmBase_VERSION`` +- The version of the IlmBase library which was found. +-``IlmBase_INCLUDE_DIRS`` +- Include directories needed to use IlmBase. +-``IlmBase_LIBRARIES`` +- Libraries needed to link to IlmBase. +-``IlmBase_LIBRARY_DIRS`` +- IlmBase library directories. +-``IlmBase_{COMPONENT}_FOUND`` +- True if the system has the named IlmBase component. +- +-Cache Variables +-^^^^^^^^^^^^^^^ +- +-The following cache variables may also be set: +- +-``IlmBase_INCLUDE_DIR`` +- The directory containing ``IlmBase/config-auto.h``. +-``IlmBase_{COMPONENT}_LIBRARY`` +- Individual component libraries for IlmBase +-``IlmBase_{COMPONENT}_DLL`` +- Individual component dlls for IlmBase on Windows. +- +-Hints +-^^^^^ +- +-Instead of explicitly setting the cache variables, the following variables +-may be provided to tell this module where to look. +- +-``ILMBASE_ROOT`` +- Preferred installation prefix. +-``ILMBASE_INCLUDEDIR`` +- Preferred include directory e.g. /include +-``ILMBASE_LIBRARYDIR`` +- Preferred library directory e.g. /lib +-``SYSTEM_LIBRARY_PATHS`` +- Paths appended to all include and lib searches. +- +-#]=======================================================================] +- +-# Support new if() IN_LIST operator +-if(POLICY CMP0057) +- cmake_policy(SET CMP0057 NEW) +-endif() +- +-mark_as_advanced( +- IlmBase_INCLUDE_DIR +- IlmBase_LIBRARY +-) +- +-set(_ILMBASE_COMPONENT_LIST +- Half +- Iex +- IexMath +- IlmThread +- Imath +-) +- +-if(IlmBase_FIND_COMPONENTS) +- set(ILMBASE_COMPONENTS_PROVIDED TRUE) +- set(_IGNORED_COMPONENTS "") +- foreach(COMPONENT ${IlmBase_FIND_COMPONENTS}) +- if(NOT ${COMPONENT} IN_LIST _ILMBASE_COMPONENT_LIST) +- list(APPEND _IGNORED_COMPONENTS ${COMPONENT}) +- endif() +- endforeach() +- +- if(_IGNORED_COMPONENTS) +- message(STATUS "Ignoring unknown components of IlmBase:") +- foreach(COMPONENT ${_IGNORED_COMPONENTS}) +- message(STATUS " ${COMPONENT}") +- endforeach() +- list(REMOVE_ITEM IlmBase_FIND_COMPONENTS ${_IGNORED_COMPONENTS}) +- endif() +-else() +- set(ILMBASE_COMPONENTS_PROVIDED FALSE) +- set(IlmBase_FIND_COMPONENTS ${_ILMBASE_COMPONENT_LIST}) +-endif() +- +-# Append ILMBASE_ROOT or $ENV{ILMBASE_ROOT} if set (prioritize the direct cmake var) +-set(_ILMBASE_ROOT_SEARCH_DIR "") +- +-if(ILMBASE_ROOT) +- list(APPEND _ILMBASE_ROOT_SEARCH_DIR ${ILMBASE_ROOT}) +-else() +- set(_ENV_ILMBASE_ROOT $ENV{ILMBASE_ROOT}) +- if(_ENV_ILMBASE_ROOT) +- list(APPEND _ILMBASE_ROOT_SEARCH_DIR ${_ENV_ILMBASE_ROOT}) +- endif() +-endif() +- +-# Additionally try and use pkconfig to find IlmBase +- +-find_package(PkgConfig) +-pkg_check_modules(PC_IlmBase QUIET IlmBase) +- +-# ------------------------------------------------------------------------ +-# Search for IlmBase include DIR +-# ------------------------------------------------------------------------ +- +-set(_ILMBASE_INCLUDE_SEARCH_DIRS "") +-list(APPEND _ILMBASE_INCLUDE_SEARCH_DIRS +- ${ILMBASE_INCLUDEDIR} +- ${_ILMBASE_ROOT_SEARCH_DIR} +- ${PC_IlmBase_INCLUDEDIR} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Look for a standard IlmBase header file. +-find_path(IlmBase_INCLUDE_DIR IlmBaseConfig.h +- NO_DEFAULT_PATH +- PATHS ${_ILMBASE_INCLUDE_SEARCH_DIRS} +- PATH_SUFFIXES include/OpenEXR OpenEXR +-) +- +-if(EXISTS "${IlmBase_INCLUDE_DIR}/IlmBaseConfig.h") +- # Get the ILMBASE version information from the config header +- file(STRINGS "${IlmBase_INCLUDE_DIR}/IlmBaseConfig.h" +- _ilmbase_version_major_string REGEX "#define ILMBASE_VERSION_MAJOR " +- ) +- string(REGEX REPLACE "#define ILMBASE_VERSION_MAJOR" "" +- _ilmbase_version_major_string "${_ilmbase_version_major_string}" +- ) +- string(STRIP "${_ilmbase_version_major_string}" IlmBase_VERSION_MAJOR) +- +- file(STRINGS "${IlmBase_INCLUDE_DIR}/IlmBaseConfig.h" +- _ilmbase_version_minor_string REGEX "#define ILMBASE_VERSION_MINOR " +- ) +- string(REGEX REPLACE "#define ILMBASE_VERSION_MINOR" "" +- _ilmbase_version_minor_string "${_ilmbase_version_minor_string}" +- ) +- string(STRIP "${_ilmbase_version_minor_string}" IlmBase_VERSION_MINOR) +- +- unset(_ilmbase_version_major_string) +- unset(_ilmbase_version_minor_string) +- +- set(IlmBase_VERSION ${IlmBase_VERSION_MAJOR}.${IlmBase_VERSION_MINOR}) +-endif() +- +-# ------------------------------------------------------------------------ +-# Search for ILMBASE lib DIR +-# ------------------------------------------------------------------------ +- +-set(_ILMBASE_LIBRARYDIR_SEARCH_DIRS "") +- +-# Append to _ILMBASE_LIBRARYDIR_SEARCH_DIRS in priority order +- +-list(APPEND _ILMBASE_LIBRARYDIR_SEARCH_DIRS +- ${ILMBASE_LIBRARYDIR} +- ${_ILMBASE_ROOT_SEARCH_DIR} +- ${PC_IlmBase_LIBDIR} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Build suffix directories +- +-set(ILMBASE_PATH_SUFFIXES +- lib64 +- lib +-) +- +-if(UNIX) +- list(INSERT ILMBASE_PATH_SUFFIXES 0 lib/x86_64-linux-gnu) +-endif() +- +-set(_ILMBASE_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) +- +-# library suffix handling +-if(WIN32) +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${IlmBase_VERSION_MAJOR}_${IlmBase_VERSION_MINOR}.lib" +- ) +-else() +- if(ILMBASE_USE_STATIC_LIBS) +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${IlmBase_VERSION_MAJOR}_${IlmBase_VERSION_MINOR}.a" +- ) +- else() +- if(APPLE) +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${IlmBase_VERSION_MAJOR}_${IlmBase_VERSION_MINOR}.dylib" +- ) +- else() +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${IlmBase_VERSION_MAJOR}_${IlmBase_VERSION_MINOR}.so" +- ) +- endif() +- endif() +-endif() +- +-set(IlmBase_LIB_COMPONENTS "") +- +-foreach(COMPONENT ${IlmBase_FIND_COMPONENTS}) +- find_library(IlmBase_${COMPONENT}_LIBRARY ${COMPONENT} +- NO_DEFAULT_PATH +- PATHS ${_ILMBASE_LIBRARYDIR_SEARCH_DIRS} +- PATH_SUFFIXES ${ILMBASE_PATH_SUFFIXES} +- ) +- list(APPEND IlmBase_LIB_COMPONENTS ${IlmBase_${COMPONENT}_LIBRARY}) +- +- if(WIN32 AND NOT ILMBASE_USE_STATIC_LIBS) +- set(_ILMBASE_TMP ${CMAKE_FIND_LIBRARY_SUFFIXES}) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") +- find_library(IlmBase_${COMPONENT}_DLL ${COMPONENT} +- NO_DEFAULT_PATH +- PATHS ${_ILMBASE_LIBRARYDIR_SEARCH_DIRS} +- PATH_SUFFIXES bin +- ) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_ILMBASE_TMP}) +- unset(_ILMBASE_TMP) +- endif() +- +- if(IlmBase_${COMPONENT}_LIBRARY) +- set(IlmBase_${COMPONENT}_FOUND TRUE) +- else() +- set(IlmBase_${COMPONENT}_FOUND FALSE) +- endif() +-endforeach() +- +-# reset lib suffix +- +-set(CMAKE_FIND_LIBRARY_SUFFIXES ${_ILMBASE_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +-unset(_ILMBASE_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) +- +-# ------------------------------------------------------------------------ +-# Cache and set ILMBASE_FOUND +-# ------------------------------------------------------------------------ +- +-include(FindPackageHandleStandardArgs) +-find_package_handle_standard_args(IlmBase +- FOUND_VAR IlmBase_FOUND +- REQUIRED_VARS +- IlmBase_INCLUDE_DIR +- IlmBase_LIB_COMPONENTS +- VERSION_VAR IlmBase_VERSION +- HANDLE_COMPONENTS +-) +- +-if(IlmBase_FOUND) +- set(IlmBase_LIBRARIES ${IlmBase_LIB_COMPONENTS}) +- +- # We have to add both include and include/OpenEXR to the include +- # path in case OpenEXR and IlmBase are installed separately +- +- set(IlmBase_INCLUDE_DIRS) +- list(APPEND IlmBase_INCLUDE_DIRS +- ${IlmBase_INCLUDE_DIR}/../ +- ${IlmBase_INCLUDE_DIR} +- ) +- set(IlmBase_DEFINITIONS ${PC_IlmBase_CFLAGS_OTHER}) +- +- set(IlmBase_LIBRARY_DIRS "") +- foreach(LIB ${IlmBase_LIB_COMPONENTS}) +- get_filename_component(_ILMBASE_LIBDIR ${LIB} DIRECTORY) +- list(APPEND IlmBase_LIBRARY_DIRS ${_ILMBASE_LIBDIR}) +- endforeach() +- list(REMOVE_DUPLICATES IlmBase_LIBRARY_DIRS) +- +- # Configure imported targets +- +- foreach(COMPONENT ${IlmBase_FIND_COMPONENTS}) +- if(NOT TARGET IlmBase::${COMPONENT}) +- add_library(IlmBase::${COMPONENT} UNKNOWN IMPORTED) +- set_target_properties(IlmBase::${COMPONENT} PROPERTIES +- IMPORTED_LOCATION "${IlmBase_${COMPONENT}_LIBRARY}" +- INTERFACE_COMPILE_OPTIONS "${IlmBase_DEFINITIONS}" +- INTERFACE_INCLUDE_DIRECTORIES "${IlmBase_INCLUDE_DIRS}" +- ) +- endif() +- endforeach() +- +-elseif(IlmBase_FIND_REQUIRED) +- message(FATAL_ERROR "Unable to find IlmBase") +-endif() +diff --git a/cmake/FindOpenEXR.cmake b/cmake/FindOpenEXR.cmake +deleted file mode 100644 +index 339c1a2..0000000 +--- a/cmake/FindOpenEXR.cmake ++++ /dev/null +@@ -1,329 +0,0 @@ +-# Copyright (c) DreamWorks Animation LLC +-# +-# All rights reserved. This software is distributed under the +-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) +-# +-# Redistributions of source code must retain the above copyright +-# and license notice and the following restrictions and disclaimer. +-# +-# * Neither the name of DreamWorks Animation nor the names of +-# its contributors may be used to endorse or promote products derived +-# from this software without specific prior written permission. +-# +-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. +-# +-#[=======================================================================[.rst: +- +-FindOpenEXR +------------ +- +-Find OpenEXR include dirs and libraries +- +-Use this module by invoking find_package with the form:: +- +- find_package(OpenEXR +- [version] [EXACT] # Minimum or EXACT version +- [REQUIRED] # Fail with error if OpenEXR is not found +- [COMPONENTS ...] # OpenEXR libraries by their canonical name +- # e.g. "IlmImf" for "libIlmImf" +- ) +- +-IMPORTED Targets +-^^^^^^^^^^^^^^^^ +- +-``OpenEXR::IlmImf`` +- The IlmImf library target. +-``OpenEXR::IlmImfUtil`` +- The IlmImfUtil library target. +- +-Result Variables +-^^^^^^^^^^^^^^^^ +- +-This will define the following variables: +- +-``OpenEXR_FOUND`` +- True if the system has the OpenEXR library. +-``OpenEXR_VERSION`` +- The version of the OpenEXR library which was found. +-``OpenEXR_INCLUDE_DIRS`` +- Include directories needed to use OpenEXR. +-``OpenEXR_LIBRARIES`` +- Libraries needed to link to OpenEXR. +-``OpenEXR_LIBRARY_DIRS`` +- OpenEXR library directories. +-``OpenEXR_DEFINITIONS`` +- Definitions to use when compiling code that uses OpenEXR. +-``OpenEXR_{COMPONENT}_FOUND`` +- True if the system has the named OpenEXR component. +- +-Cache Variables +-^^^^^^^^^^^^^^^ +- +-The following cache variables may also be set: +- +-``OpenEXR_INCLUDE_DIR`` +- The directory containing ``OpenEXR/config-auto.h``. +-``OpenEXR_{COMPONENT}_LIBRARY`` +- Individual component libraries for OpenEXR +-``OpenEXR_{COMPONENT}_DLL`` +- Individual component dlls for OpenEXR on Windows. +- +-Hints +-^^^^^ +- +-Instead of explicitly setting the cache variables, the following variables +-may be provided to tell this module where to look. +- +-``OPENEXR_ROOT`` +- Preferred installation prefix. +-``OPENEXR_INCLUDEDIR`` +- Preferred include directory e.g. /include +-``OPENEXR_LIBRARYDIR`` +- Preferred library directory e.g. /lib +-``SYSTEM_LIBRARY_PATHS`` +- Paths appended to all include and lib searches. +- +-#]=======================================================================] +- +-# Support new if() IN_LIST operator +-if(POLICY CMP0057) +- cmake_policy(SET CMP0057 NEW) +-endif() +- +-mark_as_advanced( +- OpenEXR_INCLUDE_DIR +- OpenEXR_LIBRARY +-) +- +-set(_OPENEXR_COMPONENT_LIST +- IlmImf +- IlmImfUtil +-) +- +-if(OpenEXR_FIND_COMPONENTS) +- set(OPENEXR_COMPONENTS_PROVIDED TRUE) +- set(_IGNORED_COMPONENTS "") +- foreach(COMPONENT ${OpenEXR_FIND_COMPONENTS}) +- if(NOT ${COMPONENT} IN_LIST _OPENEXR_COMPONENT_LIST) +- list(APPEND _IGNORED_COMPONENTS ${COMPONENT}) +- endif() +- endforeach() +- +- if(_IGNORED_COMPONENTS) +- message(STATUS "Ignoring unknown components of OpenEXR:") +- foreach(COMPONENT ${_IGNORED_COMPONENTS}) +- message(STATUS " ${COMPONENT}") +- endforeach() +- list(REMOVE_ITEM OpenEXR_FIND_COMPONENTS ${_IGNORED_COMPONENTS}) +- endif() +-else() +- set(OPENEXR_COMPONENTS_PROVIDED FALSE) +- set(OpenEXR_FIND_COMPONENTS ${_OPENEXR_COMPONENT_LIST}) +-endif() +- +-# Append OPENEXR_ROOT or $ENV{OPENEXR_ROOT} if set (prioritize the direct cmake var) +-set(_OPENEXR_ROOT_SEARCH_DIR "") +- +-if(OPENEXR_ROOT) +- list(APPEND _OPENEXR_ROOT_SEARCH_DIR ${OPENEXR_ROOT}) +-else() +- set(_ENV_OPENEXR_ROOT $ENV{OPENEXR_ROOT}) +- if(_ENV_OPENEXR_ROOT) +- list(APPEND _OPENEXR_ROOT_SEARCH_DIR ${_ENV_OPENEXR_ROOT}) +- endif() +-endif() +- +-# Additionally try and use pkconfig to find OpenEXR +- +-find_package(PkgConfig) +-pkg_check_modules(PC_OpenEXR QUIET OpenEXR) +- +-# ------------------------------------------------------------------------ +-# Search for OpenEXR include DIR +-# ------------------------------------------------------------------------ +- +-set(_OPENEXR_INCLUDE_SEARCH_DIRS "") +-list(APPEND _OPENEXR_INCLUDE_SEARCH_DIRS +- ${OPENEXR_INCLUDEDIR} +- ${_OPENEXR_ROOT_SEARCH_DIR} +- ${PC_OpenEXR_INCLUDEDIR} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Look for a standard OpenEXR header file. +-find_path(OpenEXR_INCLUDE_DIR OpenEXRConfig.h +- NO_DEFAULT_PATH +- PATHS ${_OPENEXR_INCLUDE_SEARCH_DIRS} +- PATH_SUFFIXES include/OpenEXR OpenEXR +-) +- +-if(EXISTS "${OpenEXR_INCLUDE_DIR}/OpenEXRConfig.h") +- # Get the EXR version information from the config header +- file(STRINGS "${OpenEXR_INCLUDE_DIR}/OpenEXRConfig.h" +- _openexr_version_major_string REGEX "#define OPENEXR_VERSION_MAJOR " +- ) +- string(REGEX REPLACE "#define OPENEXR_VERSION_MAJOR" "" +- _openexr_version_major_string "${_openexr_version_major_string}" +- ) +- string(STRIP "${_openexr_version_major_string}" OpenEXR_VERSION_MAJOR) +- +- file(STRINGS "${OpenEXR_INCLUDE_DIR}/OpenEXRConfig.h" +- _openexr_version_minor_string REGEX "#define OPENEXR_VERSION_MINOR " +- ) +- string(REGEX REPLACE "#define OPENEXR_VERSION_MINOR" "" +- _openexr_version_minor_string "${_openexr_version_minor_string}" +- ) +- string(STRIP "${_openexr_version_minor_string}" OpenEXR_VERSION_MINOR) +- +- unset(_openexr_version_major_string) +- unset(_openexr_version_minor_string) +- +- set(OpenEXR_VERSION ${OpenEXR_VERSION_MAJOR}.${OpenEXR_VERSION_MINOR}) +-endif() +- +-# ------------------------------------------------------------------------ +-# Search for OPENEXR lib DIR +-# ------------------------------------------------------------------------ +- +-set(_OPENEXR_LIBRARYDIR_SEARCH_DIRS "") +- +-# Append to _OPENEXR_LIBRARYDIR_SEARCH_DIRS in priority order +- +-list(APPEND _OPENEXR_LIBRARYDIR_SEARCH_DIRS +- ${OPENEXR_LIBRARYDIR} +- ${_OPENEXR_ROOT_SEARCH_DIR} +- ${PC_OpenEXR_LIBDIR} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Build suffix directories +- +-set(OPENEXR_PATH_SUFFIXES +- lib64 +- lib +-) +- +-if(UNIX ) +- list(INSERT OPENEXR_PATH_SUFFIXES 0 lib/x86_64-linux-gnu) +-endif() +- +-set(_OPENEXR_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) +- +-# library suffix handling +-if(WIN32) +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}.lib" +- ) +-else() +- if(OPENEXR_USE_STATIC_LIBS) +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}.a" +- ) +- else() +- if(APPLE) +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}.dylib" +- ) +- else() +- list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES +- "-${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}.so" +- ) +- endif() +- endif() +-endif() +- +-set(OpenEXR_LIB_COMPONENTS "") +- +-foreach(COMPONENT ${OpenEXR_FIND_COMPONENTS}) +- find_library(OpenEXR_${COMPONENT}_LIBRARY ${COMPONENT} +- NO_DEFAULT_PATH +- PATHS ${_OPENEXR_LIBRARYDIR_SEARCH_DIRS} +- PATH_SUFFIXES ${OPENEXR_PATH_SUFFIXES} +- ) +- list(APPEND OpenEXR_LIB_COMPONENTS ${OpenEXR_${COMPONENT}_LIBRARY}) +- +- if(WIN32 AND NOT OPENEXR_USE_STATIC_LIBS) +- set(_OPENEXR_TMP ${CMAKE_FIND_LIBRARY_SUFFIXES}) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") +- find_library(OpenEXR_${COMPONENT}_DLL ${COMPONENT} +- NO_DEFAULT_PATH +- PATHS ${_OPENEXR_LIBRARYDIR_SEARCH_DIRS} +- PATH_SUFFIXES bin +- ) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_OPENEXR_TMP}) +- unset(_OPENEXR_TMP) +- endif() +- +- if(OpenEXR_${COMPONENT}_LIBRARY) +- set(OpenEXR_${COMPONENT}_FOUND TRUE) +- else() +- set(OpenEXR_${COMPONENT}_FOUND FALSE) +- endif() +-endforeach() +- +-# reset lib suffix +- +-set(CMAKE_FIND_LIBRARY_SUFFIXES ${_OPENEXR_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +-unset(_OPENEXR_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) +- +-# ------------------------------------------------------------------------ +-# Cache and set OPENEXR_FOUND +-# ------------------------------------------------------------------------ +- +-include(FindPackageHandleStandardArgs) +-find_package_handle_standard_args(OpenEXR +- FOUND_VAR OpenEXR_FOUND +- REQUIRED_VARS +- OpenEXR_INCLUDE_DIR +- OpenEXR_LIB_COMPONENTS +- VERSION_VAR OpenEXR_VERSION +- HANDLE_COMPONENTS +-) +- +-if(OpenEXR_FOUND) +- set(OpenEXR_LIBRARIES ${OpenEXR_LIB_COMPONENTS}) +- +- # We have to add both include and include/OpenEXR to the include +- # path in case OpenEXR and IlmBase are installed separately +- +- set(OpenEXR_INCLUDE_DIRS) +- list(APPEND OpenEXR_INCLUDE_DIRS +- ${OpenEXR_INCLUDE_DIR}/../ +- ${OpenEXR_INCLUDE_DIR} +- ) +- set(OpenEXR_DEFINITIONS ${PC_OpenEXR_CFLAGS_OTHER}) +- +- set(OpenEXR_LIBRARY_DIRS "") +- foreach(LIB ${OpenEXR_LIB_COMPONENTS}) +- get_filename_component(_OPENEXR_LIBDIR ${LIB} DIRECTORY) +- list(APPEND OpenEXR_LIBRARY_DIRS ${_OPENEXR_LIBDIR}) +- endforeach() +- list(REMOVE_DUPLICATES OpenEXR_LIBRARY_DIRS) +- +- # Configure imported target +- +- foreach(COMPONENT ${OpenEXR_FIND_COMPONENTS}) +- if(NOT TARGET OpenEXR::${COMPONENT}) +- add_library(OpenEXR::${COMPONENT} UNKNOWN IMPORTED) +- set_target_properties(OpenEXR::${COMPONENT} PROPERTIES +- IMPORTED_LOCATION "${OpenEXR_${COMPONENT}_LIBRARY}" +- INTERFACE_COMPILE_OPTIONS "${OpenEXR_DEFINITIONS}" +- INTERFACE_INCLUDE_DIRECTORIES "${OpenEXR_INCLUDE_DIRS}" +- ) +- endif() +- endforeach() +-elseif(OpenEXR_FIND_REQUIRED) +- message(FATAL_ERROR "Unable to find OpenEXR") +-endif() +diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake +index 63a2eda..6211071 100644 +--- a/cmake/FindOpenVDB.cmake ++++ b/cmake/FindOpenVDB.cmake +@@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "") + + foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) + set(LIB_NAME ${COMPONENT}) +- find_library(OpenVDB_${COMPONENT}_LIBRARY ${LIB_NAME} ++ find_library(OpenVDB_${COMPONENT}_LIBRARY ${LIB_NAME} lib${LIB_NAME} + NO_DEFAULT_PATH + PATHS ${_OPENVDB_LIBRARYDIR_SEARCH_DIRS} + PATH_SUFFIXES ${OPENVDB_PATH_SUFFIXES} +@@ -282,16 +282,13 @@ find_package_handle_standard_args(OpenVDB + # ------------------------------------------------------------------------ + + # Set the ABI number the library was built against. Uses vdb_print ++find_program(OPENVDB_PRINT vdb_print ++ PATHS ${_OPENVDB_INSTALL}/bin ${OpenVDB_INCLUDE_DIR} ++ NO_DEFAULT_PATH) + + if(_OPENVDB_INSTALL) + OPENVDB_ABI_VERSION_FROM_PRINT( +- "${_OPENVDB_INSTALL}/bin/vdb_print" +- ABI OpenVDB_ABI +- ) +-else() +- # Try and find vdb_print from the include path +- OPENVDB_ABI_VERSION_FROM_PRINT( +- "${OpenVDB_INCLUDE_DIR}/../bin/vdb_print" ++ "${OPENVDB_PRINT}" + ABI OpenVDB_ABI + ) + endif() +@@ -472,6 +469,12 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) + INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers) + INTERFACE_COMPILE_FEATURES cxx_std_11 + ) ++ ++ if (OPENVDB_USE_STATIC_LIBS) ++ set_target_properties(OpenVDB::${COMPONENT} PROPERTIES ++ INTERFACE_COMPILE_DEFINITIONS "OPENVDB_STATICLIB;OPENVDB_OPENEXR_STATICLIB" ++ ) ++ endif() + endif() + endforeach() + +diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake +index bdf9c81..c6bdec9 100644 +--- a/cmake/FindTBB.cmake ++++ b/cmake/FindTBB.cmake +@@ -1,333 +1,332 @@ +-# Copyright (c) DreamWorks Animation LLC ++# The MIT License (MIT) + # +-# All rights reserved. This software is distributed under the +-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) ++# Copyright (c) 2015 Justus Calvin ++# ++# Permission is hereby granted, free of charge, to any person obtaining a copy ++# of this software and associated documentation files (the "Software"), to deal ++# in the Software without restriction, including without limitation the rights ++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++# copies of the Software, and to permit persons to whom the Software is ++# furnished to do so, subject to the following conditions: ++# ++# The above copyright notice and this permission notice shall be included in all ++# copies or substantial portions of the Software. ++# ++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++# SOFTWARE. ++ + # +-# Redistributions of source code must retain the above copyright +-# and license notice and the following restrictions and disclaimer. ++# FindTBB ++# ------- + # +-# * Neither the name of DreamWorks Animation nor the names of +-# its contributors may be used to endorse or promote products derived +-# from this software without specific prior written permission. ++# Find TBB include directories and libraries. + # +-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, +-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE +-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. ++# Usage: + # +-#[=======================================================================[.rst: +- +-FindTBB +-------- +- +-Find Tbb include dirs and libraries +- +-Use this module by invoking find_package with the form:: +- +- find_package(TBB +- [version] [EXACT] # Minimum or EXACT version +- [REQUIRED] # Fail with error if Tbb is not found +- [COMPONENTS ...] # Tbb libraries by their canonical name +- # e.g. "tbb" for "libtbb" +- ) +- +-IMPORTED Targets +-^^^^^^^^^^^^^^^^ +- +-``TBB::tbb`` +- The tbb library target. +-``TBB::tbbmalloc`` +- The tbbmalloc library target. +-``TBB::tbbmalloc_proxy`` +- The tbbmalloc_proxy library target. +- +-Result Variables +-^^^^^^^^^^^^^^^^ +- +-This will define the following variables: +- +-``Tbb_FOUND`` +- True if the system has the Tbb library. +-``Tbb_VERSION`` +- The version of the Tbb library which was found. +-``Tbb_INCLUDE_DIRS`` +- Include directories needed to use Tbb. +-``Tbb_LIBRARIES`` +- Libraries needed to link to Tbb. +-``Tbb_LIBRARY_DIRS`` +- Tbb library directories. +-``TBB_{COMPONENT}_FOUND`` +- True if the system has the named TBB component. +- +-Cache Variables +-^^^^^^^^^^^^^^^ +- +-The following cache variables may also be set: +- +-``Tbb_INCLUDE_DIR`` +- The directory containing ``tbb/tbb_stddef.h``. +-``Tbb_{COMPONENT}_LIBRARY`` +- Individual component libraries for Tbb +- +-Hints +-^^^^^ +- +-Instead of explicitly setting the cache variables, the following variables +-may be provided to tell this module where to look. +- +-``TBB_ROOT`` +- Preferred installation prefix. +-``TBB_INCLUDEDIR`` +- Preferred include directory e.g. /include +-``TBB_LIBRARYDIR`` +- Preferred library directory e.g. /lib +-``SYSTEM_LIBRARY_PATHS`` +- Paths appended to all include and lib searches. +- +-#]=======================================================================] +- +-# Support new if() IN_LIST operator +-if(POLICY CMP0057) +- cmake_policy(SET CMP0057 NEW) +-endif() ++# find_package(TBB [major[.minor]] [EXACT] ++# [QUIET] [REQUIRED] ++# [[COMPONENTS] [components...]] ++# [OPTIONAL_COMPONENTS components...]) ++# ++# where the allowed components are tbbmalloc and tbb_preview. Users may modify ++# the behavior of this module with the following variables: ++# ++# * TBB_ROOT_DIR - The base directory the of TBB installation. ++# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. ++# * TBB_LIBRARY - The directory that contains the TBB library files. ++# * TBB__LIBRARY - The path of the TBB the corresponding TBB library. ++# These libraries, if specified, override the ++# corresponding library search results, where ++# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, ++# tbb_preview, or tbb_preview_debug. ++# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will ++# be used instead of the release version. ++# * TBB_STATIC - Static linking of libraries with a _static suffix. ++# For example, on Windows a tbb_static.lib will be searched for ++# instead of tbb.lib. ++# ++# Users may modify the behavior of this module with the following environment ++# variables: ++# ++# * TBB_INSTALL_DIR ++# * TBBROOT ++# * LIBRARY_PATH ++# ++# This module will set the following variables: ++# ++# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or ++# don’t want to use TBB. ++# * TBB__FOUND - If False, optional part of TBB sytem is ++# not available. ++# * TBB_VERSION - The full version string ++# * TBB_VERSION_MAJOR - The major version ++# * TBB_VERSION_MINOR - The minor version ++# * TBB_INTERFACE_VERSION - The interface version number defined in ++# tbb/tbb_stddef.h. ++# * TBB__LIBRARY_RELEASE - The path of the TBB release version of ++# , where may be tbb, tbb_debug, ++# tbbmalloc, tbbmalloc_debug, tbb_preview, or ++# tbb_preview_debug. ++# * TBB__LIBRARY_DEGUG - The path of the TBB release version of ++# , where may be tbb, tbb_debug, ++# tbbmalloc, tbbmalloc_debug, tbb_preview, or ++# tbb_preview_debug. ++# ++# The following varibles should be used to build and link with TBB: ++# ++# * TBB_INCLUDE_DIRS - The include directory for TBB. ++# * TBB_LIBRARIES - The libraries to link against to use TBB. ++# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. ++# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. ++# * TBB_DEFINITIONS - Definitions to use when compiling code that uses ++# TBB. ++# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that ++# uses TBB. ++# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that ++# uses TBB. ++# ++# This module will also create the "tbb" target that may be used when building ++# executables and libraries. + +-mark_as_advanced( +- Tbb_INCLUDE_DIR +- Tbb_LIBRARY +-) +- +-set(_TBB_COMPONENT_LIST +- tbb +- tbbmalloc +- tbbmalloc_proxy +-) +- +-if(TBB_FIND_COMPONENTS) +- set(_TBB_COMPONENTS_PROVIDED TRUE) +- set(_IGNORED_COMPONENTS "") +- foreach(COMPONENT ${TBB_FIND_COMPONENTS}) +- if(NOT ${COMPONENT} IN_LIST _TBB_COMPONENT_LIST) +- list(APPEND _IGNORED_COMPONENTS ${COMPONENT}) +- endif() +- endforeach() ++unset(TBB_FOUND CACHE) ++unset(TBB_INCLUDE_DIRS CACHE) ++unset(TBB_LIBRARIES) ++unset(TBB_LIBRARIES_DEBUG) ++unset(TBB_LIBRARIES_RELEASE) + +- if(_IGNORED_COMPONENTS) +- message(STATUS "Ignoring unknown components of TBB:") +- foreach(COMPONENT ${_IGNORED_COMPONENTS}) +- message(STATUS " ${COMPONENT}") +- endforeach() +- list(REMOVE_ITEM TBB_FIND_COMPONENTS ${_IGNORED_COMPONENTS}) +- endif() +-else() +- set(_TBB_COMPONENTS_PROVIDED FALSE) +- set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST}) +-endif() ++include(FindPackageHandleStandardArgs) ++ ++find_package(Threads QUIET REQUIRED) + +-# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var) +-set(_TBB_ROOT_SEARCH_DIR "") ++if(NOT TBB_FOUND) + +-if(TBB_ROOT) +- list(APPEND _TBB_ROOT_SEARCH_DIR ${TBB_ROOT}) +-else() +- set(_ENV_TBB_ROOT $ENV{TBB_ROOT}) +- if(_ENV_TBB_ROOT) +- list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT}) ++ ################################## ++ # Check the build type ++ ################################## ++ ++ if(NOT DEFINED TBB_USE_DEBUG_BUILD) ++ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)") ++ set(TBB_BUILD_TYPE DEBUG) ++ else() ++ set(TBB_BUILD_TYPE RELEASE) ++ endif() ++ elseif(TBB_USE_DEBUG_BUILD) ++ set(TBB_BUILD_TYPE DEBUG) ++ else() ++ set(TBB_BUILD_TYPE RELEASE) + endif() +-endif() ++ ++ ################################## ++ # 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 +- +-find_package(PkgConfig) +-pkg_check_modules(PC_Tbb QUIET tbb) +- +-# ------------------------------------------------------------------------ +-# Search for tbb include DIR +-# ------------------------------------------------------------------------ +- +-set(_TBB_INCLUDE_SEARCH_DIRS "") +-list(APPEND _TBB_INCLUDE_SEARCH_DIRS +- ${TBB_INCLUDEDIR} +- ${_TBB_ROOT_SEARCH_DIR} +- ${PC_Tbb_INCLUDE_DIRS} +- ${SYSTEM_LIBRARY_PATHS} +-) +- +-# Look for a standard tbb header file. +-find_path(Tbb_INCLUDE_DIR tbb/tbb_stddef.h +- NO_DEFAULT_PATH +- PATHS ${_TBB_INCLUDE_SEARCH_DIRS} +- PATH_SUFFIXES include +-) +- +-if(EXISTS "${Tbb_INCLUDE_DIR}/tbb/tbb_stddef.h") +- file(STRINGS "${Tbb_INCLUDE_DIR}/tbb/tbb_stddef.h" +- _tbb_version_major_string REGEX "#define TBB_VERSION_MAJOR " +- ) +- string(REGEX REPLACE "#define TBB_VERSION_MAJOR" "" +- _tbb_version_major_string "${_tbb_version_major_string}" +- ) +- string(STRIP "${_tbb_version_major_string}" Tbb_VERSION_MAJOR) +- +- file(STRINGS "${Tbb_INCLUDE_DIR}/tbb/tbb_stddef.h" +- _tbb_version_minor_string REGEX "#define TBB_VERSION_MINOR " +- ) +- string(REGEX REPLACE "#define TBB_VERSION_MINOR" "" +- _tbb_version_minor_string "${_tbb_version_minor_string}" +- ) +- string(STRIP "${_tbb_version_minor_string}" Tbb_VERSION_MINOR) +- +- unset(_tbb_version_major_string) +- unset(_tbb_version_minor_string) +- +- set(Tbb_VERSION ${Tbb_VERSION_MAJOR}.${Tbb_VERSION_MINOR}) +-endif() ++ # Set the TBB search library path search suffix based on the version of VC ++ if(WINDOWS_STORE) ++ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") ++ elseif(MSVC14) ++ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") ++ elseif(MSVC12) ++ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") ++ elseif(MSVC11) ++ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") ++ elseif(MSVC10) ++ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") ++ endif() + +-# ------------------------------------------------------------------------ +-# Search for TBB lib DIR +-# ------------------------------------------------------------------------ ++ # Add the library path search suffix for the VC independent version of TBB ++ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") ++ ++ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") ++ # OS X ++ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") ++ ++ # TODO: Check to see which C++ library is being used by the compiler. ++ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) ++ # The default C++ library on OS X 10.9 and later is libc++ ++ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") ++ else() ++ set(TBB_LIB_PATH_SUFFIX "lib") ++ endif() ++ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") ++ # Linux ++ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") ++ ++ # TODO: Check compiler version to see the suffix should be /gcc4.1 or ++ # /gcc4.1. For now, assume that the compiler is more recent than ++ # gcc 4.4.x or later. ++ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") ++ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") ++ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") ++ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") ++ endif() ++ endif() ++ ++ ################################## ++ # Find the TBB include dir ++ ################################## ++ ++ find_path(TBB_INCLUDE_DIRS tbb/tbb.h ++ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} ++ PATHS ${TBB_DEFAULT_SEARCH_DIR} ++ PATH_SUFFIXES include) ++ ++ ################################## ++ # Set version strings ++ ################################## ++ ++ if(TBB_INCLUDE_DIRS) ++ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) ++ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" ++ TBB_VERSION_MAJOR "${_tbb_version_file}") ++ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" ++ TBB_VERSION_MINOR "${_tbb_version_file}") ++ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" ++ TBB_INTERFACE_VERSION "${_tbb_version_file}") ++ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") ++ endif() + +-set(_TBB_LIBRARYDIR_SEARCH_DIRS "") ++ ################################## ++ # Find TBB components ++ ################################## + +-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order ++ if(TBB_VERSION VERSION_LESS 4.3) ++ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) ++ else() ++ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) ++ endif() + +-set(_TBB_LIBRARYDIR_SEARCH_DIRS "") +-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS +- ${TBB_LIBRARYDIR} +- ${_TBB_ROOT_SEARCH_DIR} +- ${PC_Tbb_LIBRARY_DIRS} +- ${SYSTEM_LIBRARY_PATHS} +-) ++ if(TBB_STATIC) ++ set(TBB_STATIC_SUFFIX "_static") ++ endif() + +-set(TBB_PATH_SUFFIXES +- lib64 +- lib +-) ++ # Find each component ++ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) ++ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") + +-# platform branching ++ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE) ++ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE) + +-if(UNIX) +- list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu) +-endif() ++ # Search for the libraries ++ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} ++ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} ++ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH ++ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + +-if(APPLE) +- if(TBB_FOR_CLANG) +- list(INSERT TBB_PATH_SUFFIXES 0 lib/libc++) +- endif() +-elseif(WIN32) +- if(MSVC10) +- set(TBB_VC_DIR vc10) +- elseif(MSVC11) +- set(TBB_VC_DIR vc11) +- elseif(MSVC12) +- set(TBB_VC_DIR vc12) +- endif() +- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/${TBB_VC_DIR}) +-else() +- if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) +- if(TBB_MATCH_COMPILER_VERSION) +- string(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${CMAKE_CXX_COMPILER_VERSION}) +- list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) +- list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) +- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc${GCC_MAJOR}.${GCC_MINOR}) +- else() +- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc4.4) +- endif() +- endif() +-endif() ++ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug ++ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} ++ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH ++ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + +-if(UNIX AND TBB_USE_STATIC_LIBS) +- set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +-endif() ++ if(TBB_${_comp}_LIBRARY_DEBUG) ++ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") ++ endif() ++ if(TBB_${_comp}_LIBRARY_RELEASE) ++ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") ++ endif() ++ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) ++ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") ++ endif() + +-set(Tbb_LIB_COMPONENTS "") +- +-foreach(COMPONENT ${TBB_FIND_COMPONENTS}) +- find_library(Tbb_${COMPONENT}_LIBRARY ${COMPONENT} +- NO_DEFAULT_PATH +- PATHS ${_TBB_LIBRARYDIR_SEARCH_DIRS} +- PATH_SUFFIXES ${TBB_PATH_SUFFIXES} +- ) +- +- # On Unix, TBB sometimes uses linker scripts instead of symlinks, so parse the linker script +- # and correct the library name if so +- if(UNIX AND EXISTS ${Tbb_${COMPONENT}_LIBRARY}) +- # Ignore files where the first four bytes equals the ELF magic number +- file(READ ${Tbb_${COMPONENT}_LIBRARY} Tbb_${COMPONENT}_HEX OFFSET 0 LIMIT 4 HEX) +- if(NOT ${Tbb_${COMPONENT}_HEX} STREQUAL "7f454c46") +- # Read the first 1024 bytes of the library and match against an "INPUT (file)" regex +- file(READ ${Tbb_${COMPONENT}_LIBRARY} Tbb_${COMPONENT}_ASCII OFFSET 0 LIMIT 1024) +- if("${Tbb_${COMPONENT}_ASCII}" MATCHES "INPUT \\(([^(]+)\\)") +- # Extract the directory and apply the matched text (in brackets) +- get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY) +- set(Tbb_${COMPONENT}_LIBRARY "${Tbb_${COMPONENT}_DIR}/${CMAKE_MATCH_1}") ++ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") ++ set(TBB_${_comp}_FOUND TRUE) ++ else() ++ set(TBB_${_comp}_FOUND FALSE) + endif() ++ ++ # 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() ++ endforeach() + +- list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY}) ++ ################################## ++ # Set compile flags and libraries ++ ################################## + +- if(Tbb_${COMPONENT}_LIBRARY) +- set(TBB_${COMPONENT}_FOUND TRUE) +- else() +- set(TBB_${COMPONENT}_FOUND FALSE) ++ set(TBB_DEFINITIONS_RELEASE "") ++ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1") ++ ++ if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) ++ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") ++ endif() ++ ++ if(NOT MSVC AND NOT TBB_LIBRARIES) ++ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE}) + endif() +-endforeach() + +-if(UNIX AND TBB_USE_STATIC_LIBS) +- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) +- unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) +-endif() ++ set(TBB_DEFINITIONS "") ++ if (MSVC AND TBB_STATIC) ++ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE) ++ endif () ++ ++ unset (TBB_STATIC_SUFFIX) ++ ++ find_package_handle_standard_args(TBB ++ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES ++ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable." ++ HANDLE_COMPONENTS ++ VERSION_VAR TBB_VERSION) ++ ++ ################################## ++ # Create targets ++ ################################## ++ ++ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) ++ add_library(TBB::tbb UNKNOWN IMPORTED) ++ set_target_properties(TBB::tbb PROPERTIES ++ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}" ++ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}" ++ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} ++ IMPORTED_LOCATION ${TBB_LIBRARIES}) ++ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) ++ set_target_properties(TBB::tbb PROPERTIES ++ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$,$>:${TBB_DEFINITIONS_DEBUG}>;$<$:${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() + +-# ------------------------------------------------------------------------ +-# Cache and set TBB_FOUND +-# ------------------------------------------------------------------------ ++ 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() + +-include(FindPackageHandleStandardArgs) +-find_package_handle_standard_args(TBB +- FOUND_VAR TBB_FOUND +- REQUIRED_VARS +- Tbb_INCLUDE_DIR +- Tbb_LIB_COMPONENTS +- VERSION_VAR Tbb_VERSION +- HANDLE_COMPONENTS +-) +- +-if(TBB_FOUND) +- set(Tbb_LIBRARIES +- ${Tbb_LIB_COMPONENTS} +- ) +- set(Tbb_INCLUDE_DIRS ${Tbb_INCLUDE_DIR}) +- set(Tbb_DEFINITIONS ${PC_Tbb_CFLAGS_OTHER}) +- +- set(Tbb_LIBRARY_DIRS "") +- foreach(LIB ${Tbb_LIB_COMPONENTS}) +- get_filename_component(_TBB_LIBDIR ${LIB} DIRECTORY) +- list(APPEND Tbb_LIBRARY_DIRS ${_TBB_LIBDIR}) +- endforeach() +- list(REMOVE_DUPLICATES Tbb_LIBRARY_DIRS) +- +- # Configure imported targets +- +- foreach(COMPONENT ${TBB_FIND_COMPONENTS}) +- if(NOT TARGET TBB::${COMPONENT}) +- add_library(TBB::${COMPONENT} UNKNOWN IMPORTED) +- set_target_properties(TBB::${COMPONENT} PROPERTIES +- IMPORTED_LOCATION "${Tbb_${COMPONENT}_LIBRARY}" +- INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}" +- INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}" +- ) +- endif() +- endforeach() +-elseif(TBB_FIND_REQUIRED) +- message(FATAL_ERROR "Unable to find TBB") + endif() +diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt +index 89301bd..df27aae 100644 +--- a/openvdb/CMakeLists.txt ++++ b/openvdb/CMakeLists.txt +@@ -78,7 +78,7 @@ else() + endif() + + find_package(TBB ${MINIMUM_TBB_VERSION} REQUIRED COMPONENTS tbb) +-if(${Tbb_VERSION} VERSION_LESS FUTURE_MINIMUM_TBB_VERSION) ++if(${TBB_VERSION} VERSION_LESS FUTURE_MINIMUM_TBB_VERSION) + message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} " + "is deprecated and will be removed.") + endif() +@@ -185,11 +185,6 @@ if(WIN32) + endif() + endif() + +-# @todo Should be target definitions +-if(WIN32) +- add_definitions(-D_WIN32 -DNOMINMAX -DOPENVDB_DLL) +-endif() +- + ##### Core library configuration + + set(OPENVDB_LIBRARY_SOURCE_FILES +@@ -374,10 +369,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES + + if(OPENVDB_CORE_SHARED) + add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES}) ++ if(WIN32) ++ target_compile_definitions(openvdb_shared PUBLIC OPENVDB_DLL) ++ endif() + endif() + + if(OPENVDB_CORE_STATIC) + add_library(openvdb_static STATIC ${OPENVDB_LIBRARY_SOURCE_FILES}) ++ if(WIN32) ++ target_compile_definitions(openvdb_static PUBLIC OPENVDB_STATICLIB) ++ endif() + endif() + + # Alias either the shared or static library to the generic OpenVDB +diff --git a/openvdb/Grid.cc b/openvdb/Grid.cc +index 0015f81..cb6084a 100644 +--- a/openvdb/Grid.cc ++++ b/openvdb/Grid.cc +@@ -35,6 +35,9 @@ + #include + #include + ++// WTF??? Somehow from stdlib.h ++#undef min ++#undef max + + namespace openvdb { + OPENVDB_USE_VERSION_NAMESPACE +diff --git a/openvdb/PlatformConfig.h b/openvdb/PlatformConfig.h +index 20ad9a3..c2dd1ef 100644 +--- a/openvdb/PlatformConfig.h ++++ b/openvdb/PlatformConfig.h +@@ -44,9 +44,12 @@ + + // By default, assume that we're dynamically linking OpenEXR, unless + // OPENVDB_OPENEXR_STATICLIB is defined. +- #if !defined(OPENVDB_OPENEXR_STATICLIB) && !defined(OPENEXR_DLL) +- #define OPENEXR_DLL +- #endif ++ // Meszaros Tamas: Why? OpenEXR and its imported targets have OPENEXR_DLL ++ // in INTERFACE_COMPILE_DEFINITIONS if build with it. ++ // #if !defined(OPENVDB_OPENEXR_STATICLIB) && !defined(OPENEXR_DLL) ++ // #define OPENEXR_DLL ++ // static_assert(false, "This is bad: OPENEXR_DLL"); ++ // #endif + + #endif // _WIN32 + +diff --git a/openvdb/cmd/CMakeLists.txt b/openvdb/cmd/CMakeLists.txt +index 57fbec0..55b3850 100644 +--- a/openvdb/cmd/CMakeLists.txt ++++ b/openvdb/cmd/CMakeLists.txt +@@ -74,8 +74,9 @@ if(WIN32) + endif() + endif() + ++# @todo Should be target definitions + if(WIN32) +- add_definitions(-D_WIN32 -DNOMINMAX -DOPENVDB_DLL) ++ add_definitions(-D_WIN32 -DNOMINMAX) + endif() + + # rpath handling +@@ -88,7 +89,6 @@ if(OPENVDB_ENABLE_RPATH) + ${IlmBase_LIBRARY_DIRS} + ${Log4cplus_LIBRARY_DIRS} + ${Blosc_LIBRARY_DIRS} +- ${Tbb_LIBRARY_DIRS} + ) + if(OPENVDB_BUILD_CORE) + list(APPEND RPATHS ${CMAKE_INSTALL_PREFIX}/lib) +diff --git a/openvdb/unittest/CMakeLists.txt b/openvdb/unittest/CMakeLists.txt +index c9e0c34..7e261c0 100644 +--- a/openvdb/unittest/CMakeLists.txt ++++ b/openvdb/unittest/CMakeLists.txt +@@ -71,8 +71,9 @@ if(WIN32) + link_directories(${Boost_LIBRARY_DIR}) + endif() + ++# @todo Should be target definitions + if(WIN32) +- add_definitions(-D_WIN32 -DNOMINMAX -DOPENVDB_DLL) ++ add_definitions(-D_WIN32 -DNOMINMAX) + endif() + + ##### VDB unit tests +diff --git a/openvdb/unittest/TestFile.cc b/openvdb/unittest/TestFile.cc +index df51830..0ab0c12 100644 +--- a/openvdb/unittest/TestFile.cc ++++ b/openvdb/unittest/TestFile.cc +@@ -2573,7 +2573,7 @@ TestFile::testBlosc() + outdata(new char[decompbufbytes]); + + for (int compcode = 0; compcode <= BLOSC_ZLIB; ++compcode) { +- char* compname = nullptr; ++ const char* compname = nullptr; + if (0 > blosc_compcode_to_compname(compcode, &compname)) continue; + /// @todo This changes the compressor setting globally. + if (blosc_set_compressor(compname) < 0) continue; +-- +2.16.2.windows.1 + diff --git a/deps/qhull-mods.patch b/deps/qhull-mods.patch index 94aeeca2f5..70f7be6a74 100644 --- a/deps/qhull-mods.patch +++ b/deps/qhull-mods.patch @@ -1,121 +1,49 @@ -From a31ae4781a4afa60e21c70e5b4ae784bcd447c8a Mon Sep 17 00:00:00 2001 +From 7f55a56b3d112f4dffbf21b1722f400c64bf03b1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros -Date: Thu, 6 Jun 2019 15:41:43 +0200 -Subject: [PATCH] prusa-slicer changes +Date: Mon, 21 Oct 2019 16:52:04 +0200 +Subject: [PATCH] Fix the build on macOS --- - CMakeLists.txt | 44 +++++++++++++++++++++++++++++++++++--- - Config.cmake.in | 2 ++ - src/libqhull_r/qhull_r-exports.def | 2 ++ - src/libqhull_r/user_r.h | 2 +- - 4 files changed, 46 insertions(+), 4 deletions(-) - create mode 100644 Config.cmake.in + CMakeLists.txt | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt -index 59dff41..20c2ec5 100644 +index 07d3da2..14df8e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -61,7 +61,7 @@ - # $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $ +@@ -626,18 +626,18 @@ install(TARGETS ${qhull_TARGETS_INSTALL} EXPORT QhullTargets + include(CMakePackageConfigHelpers) - project(qhull) --cmake_minimum_required(VERSION 2.6) -+cmake_minimum_required(VERSION 3.0) + write_basic_package_version_file( +- "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/QhullExport/QhullConfigVersion.cmake" + VERSION ${qhull_VERSION} + COMPATIBILITY AnyNewerVersion + ) - # Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri - set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c -@@ -610,10 +610,48 @@ add_test(NAME user_eg3 - # Define install - # --------------------------------------- + export(EXPORT QhullTargets +- FILE "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullTargets.cmake" ++ FILE "${CMAKE_CURRENT_BINARY_DIR}/QhullExport/QhullTargets.cmake" + NAMESPACE Qhull:: + ) --install(TARGETS ${qhull_TARGETS_INSTALL} -+install(TARGETS ${qhull_TARGETS_INSTALL} EXPORT QhullTargets - RUNTIME DESTINATION ${BIN_INSTALL_DIR} - LIBRARY DESTINATION ${LIB_INSTALL_DIR} -- ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) -+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} -+ INCLUDES DESTINATION include) -+ -+include(CMakePackageConfigHelpers) -+ -+write_basic_package_version_file( -+ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" -+ VERSION ${qhull_VERSION} -+ COMPATIBILITY AnyNewerVersion -+) -+ -+export(EXPORT QhullTargets -+ FILE "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullTargets.cmake" -+ NAMESPACE Qhull:: -+) -+ -+configure_file(Config.cmake.in -+ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" -+ @ONLY -+) -+ -+set(ConfigPackageLocation lib/cmake/Qhull) -+install(EXPORT QhullTargets -+ FILE -+ QhullTargets.cmake -+ NAMESPACE -+ Qhull:: -+ DESTINATION -+ ${ConfigPackageLocation} -+) -+install( -+ FILES -+ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" -+ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" -+ DESTINATION -+ ${ConfigPackageLocation} -+ COMPONENT -+ Devel -+) + configure_file(${PROJECT_SOURCE_DIR}/build/config.cmake.in +- "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/QhullExport/QhullConfig.cmake" + @ONLY + ) - install(FILES ${libqhull_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/libqhull) - install(FILES ${libqhull_DOC} DESTINATION ${INCLUDE_INSTALL_DIR}/libqhull) -diff --git a/Config.cmake.in b/Config.cmake.in -new file mode 100644 -index 0000000..bc92bfe ---- /dev/null -+++ b/Config.cmake.in -@@ -0,0 +1,2 @@ -+include("${CMAKE_CURRENT_LIST_DIR}/QhullTargets.cmake") -+ -diff --git a/src/libqhull_r/qhull_r-exports.def b/src/libqhull_r/qhull_r-exports.def -index 325d57c..72f6ad0 100644 ---- a/src/libqhull_r/qhull_r-exports.def -+++ b/src/libqhull_r/qhull_r-exports.def -@@ -185,6 +185,7 @@ qh_memsetup - qh_memsize - qh_memstatistics - qh_memtotal -+qh_memcheck - qh_merge_degenredundant - qh_merge_nonconvex - qh_mergecycle -@@ -372,6 +373,7 @@ qh_settruncate - qh_setunique - qh_setvoronoi_all - qh_setzero -+qh_setendpointer - qh_sharpnewfacets - qh_skipfacet - qh_skipfilename -diff --git a/src/libqhull_r/user_r.h b/src/libqhull_r/user_r.h -index fc105b9..7cca65a 100644 ---- a/src/libqhull_r/user_r.h -+++ b/src/libqhull_r/user_r.h -@@ -139,7 +139,7 @@ Code flags -- - REALfloat = 1 all numbers are 'float' type - = 0 all numbers are 'double' type - */ --#define REALfloat 0 -+#define REALfloat 1 - - #if (REALfloat == 1) - #define realT float +@@ -652,8 +652,8 @@ install(EXPORT QhullTargets + ) + install( + FILES +- "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" +- "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/QhullExport/QhullConfig.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/QhullExport/QhullConfigVersion.cmake" + DESTINATION + ${ConfigPackageLocation} + COMPONENT -- -2.16.2.windows.1 +2.17.1 diff --git a/doc/Dependencies.md b/doc/Dependencies.md index b4b0c348cb..3f6335cb73 100644 --- a/doc/Dependencies.md +++ b/doc/Dependencies.md @@ -1,6 +1,6 @@ # Dependency report for PrusaSlicer ## Possible dynamic linking on Linux -* zlib: This should not be even mentioned in our cmake scripts but due to a bug in the system libraries of gtk it has to be linked to PrusaSlicer. +* zlib: Strict dependency required from the system, linked dynamically. Many other libs depend on zlib. * wxWidgets: searches for wx-3.1 by default, but with cmake option `SLIC3R_WX_STABLE=ON` it will use wx-3.0 bundled with most distros. * libcurl * tbb @@ -10,13 +10,13 @@ * expat * openssl * nlopt -* gtest +* openvdb: This library depends on other libs, namely boost, zlib, openexr, blosc (not strictly), etc... ## External libraries in source tree * ad-mesh: Lots of customization, have to be bundled in the source tree. * avrdude: Like ad-mesh, many customization, need to be in the source tree. * clipper: An important library we have to have full control over it. We also have some slicer specific modifications. -* glu-libtess: This is an extract of the mesa/glu library not oficially available as a package. +* glu-libtess: This is an extract of the mesa/glu library not officially available as a package. * imgui: no packages for debian, author suggests using in the source tree * miniz: No packages, author suggests using in the source tree * qhull: libqhull-dev does not contain libqhullcpp => link errors. Until it is fixed, we will use the builtin version. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925540 @@ -29,5 +29,6 @@ * igl * nanosvg * agg +* catch2: Only Arch has packages for catch2, other distros at most catch (v1.x). Being strictly header only, we bundle this in the source tree. Used for the unit-test suites. diff --git a/doc/How to build - Linux et al.md b/doc/How to build - Linux et al.md index 715b1388b3..9206ae1ed2 100644 --- a/doc/How to build - Linux et al.md +++ b/doc/How to build - Linux et al.md @@ -2,7 +2,7 @@ # Building PrusaSlicer on UNIX/Linux PrusaSlicer uses the CMake build system and requires several dependencies. -The dependencies can be listed in `deps/deps-linux.cmake`, although they don't necessarily need to be as recent +The dependencies can be listed in `deps/deps-linux.cmake` and `deps/deps-unix-common.cmake`, although they don't necessarily need to be as recent as the versions listed - generally versions available on conservative Linux distros such as Debian stable or CentOS should suffice. Perl is not required any more. diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 82f109df44..aa2f938b7e 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -45,6 +45,7 @@ src/slic3r/GUI/WipeTowerDialog.cpp src/slic3r/GUI/wxExtensions.cpp src/slic3r/Utils/Duet.cpp src/slic3r/Utils/OctoPrint.cpp +src/slic3r/Utils/FlashAir.cpp src/slic3r/Utils/PresetUpdater.cpp src/slic3r/Utils/FixModelByWin10.cpp src/libslic3r/Zipper.cpp diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 406fd93c80..9add73b4dc 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -82,6 +82,29 @@ variants = default technology = SLA family = SL1 +[default_filaments] +Generic PLA = 1 +Generic PLA MMU2 = 1 +Prusa PLA = 1 +Prusa PLA MMU2 = 1 +Prusament PLA = 1 +Prusament PLA MMU2 = 1 + +[default_sla_materials] +Prusa Azure Blue Tough 0.05 = 1 +Prusa Black Tough 0.05 = 1 +Prusa Green Casting 0.05 = 1 +Prusa Grey Tough 0.05 = 1 +Prusa Maroon Tough 0.05 = 1 +Prusa Orange Tough 0.025 = 1 +Prusa Orange Tough 0.035 = 1 +Prusa Orange Tough 0.05 = 1 +Prusa Orange Tough 0.1 = 1 +Prusa Pink Tough 0.05 = 1 +Prusa Skin Tough 0.05 = 1 +Prusa Transparent Red Tough 0.05 = 1 +Prusa White Tough 0.05 = 1 + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -1128,6 +1151,7 @@ filament_density = 3.9 filament_colour = #804040 filament_max_volumetric_speed = 9 filament_notes = "List of materials tested with standard print settings:\n\nColorFabb bronzeFill\nColorFabb brassFill\nColorFabb steelFill\nColorFabb copperFill" +filament_vendor = ColorFabb [filament:ColorFabb HT] inherits = *PET* @@ -1145,11 +1169,13 @@ max_fan_speed = 20 min_fan_speed = 10 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 +filament_vendor = ColorFabb [filament:ColorFabb PLA-PHA] inherits = *PLA* filament_cost = 55.5 filament_density = 1.24 +filament_vendor = ColorFabb [filament:ColorFabb woodFill] inherits = *PLA* @@ -1163,6 +1189,7 @@ filament_max_volumetric_speed = 10 first_layer_temperature = 200 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 +filament_vendor = ColorFabb [filament:ColorFabb corkFill] inherits = *PLA* @@ -1175,6 +1202,7 @@ filament_max_volumetric_speed = 6 first_layer_temperature = 220 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 +filament_vendor = ColorFabb [filament:ColorFabb XT] inherits = *PET* @@ -1184,6 +1212,7 @@ filament_density = 1.27 first_layer_bed_temperature = 90 first_layer_temperature = 260 temperature = 270 +filament_vendor = ColorFabb [filament:ColorFabb XT-CF20] inherits = *PET* @@ -1199,6 +1228,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{el temperature = 260 filament_retract_length = nil filament_retract_lift = 0.2 +filament_vendor = ColorFabb [filament:ColorFabb nGen] inherits = *PET* @@ -1211,6 +1241,7 @@ filament_type = NGEN first_layer_temperature = 240 max_fan_speed = 35 min_fan_speed = 20 +filament_vendor = ColorFabb [filament:ColorFabb nGen flex] inherits = *FLEX* @@ -1231,12 +1262,14 @@ temperature = 260 filament_retract_length = nil filament_retract_lift = 0 compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +filament_vendor = ColorFabb [filament:E3D Edge] inherits = *PET* filament_cost = 56.9 filament_density = 1.26 filament_type = EDGE +filament_vendor = E3D [filament:E3D PC-ABS] inherits = *ABS* @@ -1245,6 +1278,7 @@ filament_type = PC filament_density = 1.05 first_layer_temperature = 270 temperature = 270 +filament_vendor = E3D [filament:Fillamentum ABS] inherits = *ABS* @@ -1252,6 +1286,7 @@ filament_cost = 32.4 filament_density = 1.04 first_layer_temperature = 240 temperature = 240 +filament_vendor = Fillamentum [filament:Fillamentum ASA] inherits = *ABS* @@ -1266,6 +1301,7 @@ slowdown_below_layer_time = 15 first_layer_temperature = 265 temperature = 265 filament_type = ASA +filament_vendor = Fillamentum [filament:Prusament ASA] inherits = *ABS* @@ -1296,6 +1332,7 @@ first_layer_temperature = 275 max_fan_speed = 50 min_fan_speed = 50 temperature = 275 +filament_vendor = Fillamentum [filament:Fillamentum Timberfill] inherits = *PLA* @@ -1309,24 +1346,28 @@ filament_max_volumetric_speed = 10 first_layer_temperature = 190 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 190 +filament_vendor = Fillamentum [filament:Generic ABS] inherits = *ABS* filament_cost = 27.82 filament_density = 1.04 filament_notes = "List of materials tested with standard ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" +filament_vendor = Generic [filament:Generic PET] inherits = *PET* filament_cost = 27.82 filament_density = 1.27 filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" +filament_vendor = Generic [filament:Generic PLA] inherits = *PLA* filament_cost = 25.4 filament_density = 1.24 filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_vendor = Generic [filament:Generic FLEX] inherits = *FLEX* @@ -1347,6 +1388,7 @@ filament_colour = #3A80CA first_layer_bed_temperature = 100 first_layer_temperature = 270 temperature = 270 +filament_vendor = Polymaker [filament:PrimaSelect PVA+] inherits = *PLA* @@ -1363,12 +1405,14 @@ filament_type = PVA first_layer_temperature = 195 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 +filament_vendor = PrimaSelect [filament:Prusa ABS] inherits = *ABS* filament_cost = 27.82 filament_density = 1.08 filament_notes = "List of materials tested with standard ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" +filament_vendor = Prusa [filament:*ABS MMU2*] inherits = Prusa ABS @@ -1385,6 +1429,7 @@ filament_unloading_speed = 20 [filament:Generic ABS MMU2] inherits = *ABS MMU2* +filament_vendor = Generic [filament:Prusament ASA MMU2] inherits = *ABS MMU2* @@ -1410,6 +1455,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{el [filament:Prusa ABS MMU2] inherits = *ABS MMU2* +filament_vendor = Prusa [filament:Prusa HIPS] inherits = *ABS* @@ -1428,6 +1474,7 @@ max_fan_speed = 20 min_fan_speed = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 +filament_vendor = Prusa [filament:Prusa PET] inherits = *PET* @@ -1435,6 +1482,7 @@ filament_cost = 27.82 filament_density = 1.27 filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG" compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +filament_vendor = Prusa [filament:Prusament PETG] inherits = *PET* @@ -1444,12 +1492,14 @@ filament_cost = 24.99 filament_density = 1.27 filament_type = PETG compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +filament_vendor = Prusa [filament:Prusa PET 0.6 nozzle] inherits = *PET06* filament_cost = 27.82 filament_density = 1.27 filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG" +filament_vendor = Prusa [filament:Prusament PETG 0.6 nozzle] inherits = *PET06* @@ -1458,6 +1508,7 @@ temperature = 250 filament_cost = 24.99 filament_density = 1.27 filament_type = PETG +filament_vendor = Prusa [filament:*PET MMU2*] inherits = Prusa PET @@ -1485,9 +1536,11 @@ filament_max_volumetric_speed = 13 [filament:Generic PET MMU2] inherits = *PET MMU2* +filament_vendor = Generic [filament:Prusa PET MMU2] inherits = *PET MMU2* +filament_vendor = Prusa [filament:Prusament PETG MMU2] inherits = *PET MMU2* @@ -1498,16 +1551,19 @@ inherits = *PET MMU2 06* [filament:Prusa PET MMU2 0.6 nozzle] inherits = *PET MMU2 06* +filament_vendor = Prusa [filament:Prusament PETG MMU2 0.6 nozzle] inherits = *PET MMU2 06* filament_type = PETG +filament_vendor = Prusa [filament:Prusa PLA] inherits = *PLA* filament_cost = 25.4 filament_density = 1.24 filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFiberlogy PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nAmazonBasics PLA" +filament_vendor = Prusa [filament:Prusament PLA] inherits = *PLA* @@ -1515,6 +1571,7 @@ temperature = 215 filament_cost = 24.99 filament_density = 1.24 filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa" +filament_vendor = Prusa [filament:*PLA MMU2*] inherits = Prusa PLA @@ -1534,18 +1591,22 @@ filament_unloading_speed_start = 100 [filament:Generic PLA MMU2] inherits = *PLA MMU2* +filament_vendor = Generic [filament:Prusa PLA MMU2] inherits = *PLA MMU2* +filament_vendor = Prusa [filament:Prusament PLA MMU2] inherits = *PLA MMU2* +filament_vendor = Prusa [filament:SemiFlex or Flexfill 98A] inherits = *FLEX* filament_cost = 82 filament_density = 1.22 filament_max_volumetric_speed = 1.35 +filament_vendor = Flexfill [filament:Taulman Bridge] inherits = *common* @@ -1567,6 +1628,7 @@ max_fan_speed = 5 min_fan_speed = 0 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 250 +filament_vendor = Taulman [filament:Taulman T-Glase] inherits = *PET* @@ -1580,6 +1642,7 @@ first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +filament_vendor = Taulman [filament:Verbatim BVOH] inherits = *common* @@ -1603,6 +1666,7 @@ max_fan_speed = 100 min_fan_speed = 100 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 +filament_vendor = Verbatim [filament:Verbatim BVOH MMU2] inherits = Verbatim BVOH @@ -1622,6 +1686,7 @@ filament_unload_time = 12 filament_unloading_speed = 20 filament_unloading_speed_start = 100 filament_loading_speed_start = 19 +filament_vendor = Verbatim [filament:PrimaSelect PVA+ MMU2] inherits = *common* @@ -1660,6 +1725,7 @@ min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 195 +filament_vendor = PrimaSelect [filament:Verbatim PP] inherits = *common* @@ -1682,6 +1748,7 @@ max_fan_speed = 100 min_fan_speed = 100 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 +filament_vendor = Verbatim ## Filaments MMU1 @@ -1899,9 +1966,11 @@ exposure_time = 6 initial_exposure_time = 40 [sla_material:BlueCast Keramaster Dental 0.025] +material_type = Dental inherits = *common 0.025* exposure_time = 6 initial_exposure_time = 45 +material_vendor = Bluecast [sla_material:BlueCast X10 0.025] inherits = *common 0.025* @@ -1912,6 +1981,7 @@ initial_exposure_time = 100 inherits = *common 0.025* exposure_time = 6 initial_exposure_time = 35 +material_vendor = Prusa [sla_material:Prusa Grey Tough 0.025] inherits = *common 0.025* @@ -1964,31 +2034,38 @@ initial_exposure_time = 35 inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 35 +material_vendor = Bluecast [sla_material:BlueCast Keramaster 0.05] inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 45 +material_vendor = Bluecast [sla_material:BlueCast Keramaster Dental 0.05] +material_type = Dental inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 50 +material_vendor = Bluecast [sla_material:BlueCast LCD-DLP Original 0.05] inherits = *common 0.05* exposure_time = 10 initial_exposure_time = 60 +material_vendor = Bluecast [sla_material:BlueCast Phrozen Wax 0.05] inherits = *common 0.05* exposure_time = 16 initial_exposure_time = 50 +material_vendor = Bluecast [sla_material:BlueCast S+ 0.05] inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 45 +material_vendor = Bluecast [sla_material:BlueCast X10 0.05] inherits = *common 0.05* @@ -1999,26 +2076,31 @@ initial_exposure_time = 100 inherits = *common 0.05* exposure_time = 6 initial_exposure_time = 40 +material_vendor = Monocure [sla_material:Monocure 3D Blue Rapid Resin 0.05] inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 40 +material_vendor = Monocure [sla_material:Monocure 3D Clear Rapid Resin 0.05] inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 40 +material_vendor = Monocure [sla_material:Monocure 3D Grey Rapid Resin 0.05] inherits = *common 0.05* exposure_time = 10 initial_exposure_time = 30 +material_vendor = Monocure [sla_material:Monocure 3D White Rapid Resin 0.05] inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 40 +material_vendor = Monocure [sla_material:3DM-HTR140 (high temperature) 0.05] inherits = *common 0.05* @@ -2034,36 +2116,43 @@ initial_exposure_time = 25 inherits = *common 0.05* exposure_time = 20 initial_exposure_time = 40 +material_vendor = 3DM [sla_material:3DM-DENT 0.05] inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 45 +material_vendor = 3DM [sla_material:3DM-HR Green 0.05] inherits = *common 0.05* exposure_time = 15 initial_exposure_time = 40 +material_vendor = 3DM [sla_material:3DM-HR Red Wine 0.05] inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 35 +material_vendor = 3DM [sla_material:3DM-XPRO White 0.05] inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 35 +material_vendor = 3DM [sla_material:FTD Ash Grey 0.05] inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 40 +material_vendor = FTD [sla_material:Harz Labs Model Resin Cherry 0.05] inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 45 +material_vendor = Harz Labs [sla_material:Photocentric Hard Grey 0.05] inherits = *common 0.05* @@ -2116,6 +2205,7 @@ initial_exposure_time = 35 inherits = *common 0.05* exposure_time = 13 initial_exposure_time = 40 +material_vendor = Prusa ## [sla_material:Prusa Yellow Solid 0.05] ## inherits = *common 0.05* @@ -2126,6 +2216,7 @@ initial_exposure_time = 40 inherits = *common 0.05* exposure_time = 7.5 initial_exposure_time = 35 +material_vendor = Prusa ## [sla_material:Prusa Transparent Green Tough 0.05] ## inherits = *common 0.05* @@ -2136,21 +2227,25 @@ initial_exposure_time = 35 inherits = *common 0.05* exposure_time = 6 initial_exposure_time = 35 +material_vendor = Prusa [sla_material:Prusa Maroon Tough 0.05] inherits = *common 0.05* exposure_time = 7.5 initial_exposure_time = 35 +material_vendor = Prusa [sla_material:Prusa Pink Tough 0.05] inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 35 +material_vendor = Prusa [sla_material:Prusa Azure Blue Tough 0.05] inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 35 +material_vendor = Prusa [sla_material:Prusa Transparent Tough 0.05] inherits = *common 0.05* @@ -2193,6 +2288,7 @@ initial_exposure_time = 15 inherits = *common 0.035* exposure_time = 6 initial_exposure_time = 35 +material_vendor = Prusa ########### Materials 0.1 @@ -2235,6 +2331,7 @@ initial_exposure_time = 55 inherits = *common 0.1* exposure_time = 8 initial_exposure_time = 35 +material_vendor = Prusa [sla_material:Prusa Green Casting 0.1] inherits = *common 0.1* diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index 5905c438e9..3372698c39 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -1,2 +1,2 @@ -add_subdirectory(slabasebed) add_subdirectory(slasupporttree) +add_subdirectory(openvdb) diff --git a/sandboxes/openvdb/CMakeLists.txt b/sandboxes/openvdb/CMakeLists.txt new file mode 100644 index 0000000000..184452e833 --- /dev/null +++ b/sandboxes/openvdb/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(openvdb_example openvdb_example.cpp) +target_link_libraries(openvdb_example libslic3r) diff --git a/sandboxes/openvdb/openvdb_example.cpp b/sandboxes/openvdb/openvdb_example.cpp new file mode 100644 index 0000000000..0df60d8aa3 --- /dev/null +++ b/sandboxes/openvdb/openvdb_example.cpp @@ -0,0 +1,37 @@ +#include +#include + +int main() +{ + // Initialize the OpenVDB library. This must be called at least + // once per program and may safely be called multiple times. + openvdb::initialize(); + // Create an empty floating-point grid with background value 0. + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(); + std::cout << "Testing random access:" << std::endl; + // Get an accessor for coordinate-based access to voxels. + openvdb::FloatGrid::Accessor accessor = grid->getAccessor(); + // Define a coordinate with large signed indices. + openvdb::Coord xyz(1000, -200000000, 30000000); + // Set the voxel value at (1000, -200000000, 30000000) to 1. + accessor.setValue(xyz, 1.0); + // Verify that the voxel value at (1000, -200000000, 30000000) is 1. + std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl; + // Reset the coordinates to those of a different voxel. + xyz.reset(1000, 200000000, -30000000); + // Verify that the voxel value at (1000, 200000000, -30000000) is + // the background value, 0. + std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl; + // Set the voxel value at (1000, 200000000, -30000000) to 2. + accessor.setValue(xyz, 2.0); + // Set the voxels at the two extremes of the available coordinate space. + // For 32-bit signed coordinates these are (-2147483648, -2147483648, -2147483648) + // and (2147483647, 2147483647, 2147483647). + accessor.setValue(openvdb::Coord::min(), 3.0f); + accessor.setValue(openvdb::Coord::max(), 4.0f); + std::cout << "Testing sequential access:" << std::endl; + // Print all active ("on") voxels by means of an iterator. + for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter; ++iter) { + std::cout << "Grid" << iter.getCoord() << " = " << *iter << std::endl; + } +} diff --git a/sandboxes/slabasebed/CMakeLists.txt b/sandboxes/slabasebed/CMakeLists.txt deleted file mode 100644 index 9d731a1333..0000000000 --- a/sandboxes/slabasebed/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_executable(slabasebed EXCLUDE_FROM_ALL slabasebed.cpp) -target_link_libraries(slabasebed libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp deleted file mode 100644 index b8b94d86f3..0000000000 --- a/sandboxes/slabasebed/slabasebed.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -const std::string USAGE_STR = { - "Usage: slabasebed stlfilename.stl" -}; - -namespace Slic3r { namespace sla { - -Contour3D create_base_pool(const Polygons &ground_layer, - const ExPolygons &holes = {}, - const PoolConfig& cfg = PoolConfig()); - -Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, - double floor_z_mm, double ceiling_z_mm, - double offset_difference_mm, ThrowOnCancel thr); - -void offset(ExPolygon& sh, coord_t distance); - -} -} - -int main(const int argc, const char *argv[]) { - using namespace Slic3r; - using std::cout; using std::endl; - - if(argc < 2) { - cout << USAGE_STR << endl; - return EXIT_SUCCESS; - } - - TriangleMesh model; - Benchmark bench; - - model.ReadSTLFile(argv[1]); - model.align_to_origin(); - - ExPolygons ground_slice; - sla::base_plate(model, ground_slice, 0.1f); - if(ground_slice.empty()) return EXIT_FAILURE; - - ground_slice = offset_ex(ground_slice, 0.5); - ExPolygon gndfirst; gndfirst = ground_slice.front(); - sla::breakstick_holes(gndfirst, 0.5, 10, 0.3); - - sla::Contour3D mesh; - - bench.start(); - - sla::PoolConfig cfg; - cfg.min_wall_height_mm = 0; - cfg.edge_radius_mm = 0; - mesh = sla::create_base_pool(to_polygons(ground_slice), {}, cfg); - - bench.stop(); - - cout << "Base pool creation time: " << std::setprecision(10) - << bench.getElapsedSec() << " seconds." << endl; - - for(auto& trind : mesh.indices) { - Vec3d p0 = mesh.points[size_t(trind[0])]; - Vec3d p1 = mesh.points[size_t(trind[1])]; - Vec3d p2 = mesh.points[size_t(trind[2])]; - Vec3d p01 = p1 - p0; - Vec3d p02 = p2 - p0; - auto a = p01.cross(p02).norm() / 2.0; - if(std::abs(a) < 1e-6) std::cout << "degenerate triangle" << std::endl; - } - - // basepool.write_ascii("out.stl"); - - std::fstream outstream("out.obj", std::fstream::out); - mesh.to_obj(outstream); - - return EXIT_SUCCESS; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31cb24f24a..1bf2787220 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,6 @@ add_subdirectory(semver) add_subdirectory(libigl) # Adding libnest2d project for bin packing... -set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") add_subdirectory(libnest2d) add_subdirectory(libslic3r) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 6c6f9584f0..f708679c18 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -167,6 +167,7 @@ int CLI::run(int argc, char **argv) // sla_print_config.apply(m_print_config, true); // Loop through transform options. + bool user_center_specified = false; for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; @@ -209,6 +210,7 @@ int CLI::run(int argc, char **argv) for (auto &model : m_models) model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default } else if (opt_key == "center") { + user_center_specified = true; for (auto &model : m_models) { model.add_default_instances(); // this affects instances: @@ -403,7 +405,9 @@ int CLI::run(int argc, char **argv) if (! m_config.opt_bool("dont_arrange")) { //FIXME make the min_object_distance configurable. model.arrange_objects(fff_print.config().min_object_distance()); - model.center_instances_around_point(m_config.option("center")->value); + model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ? + BoundingBoxf(m_print_config.opt("bed_shape")->values).center() : + m_config.option("center")->value); } if (printer_technology == ptFFF) { for (auto* mo : model.objects) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index a6989ca6ed..fa0edec2ba 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -90,7 +90,7 @@ struct stl_neighbors { struct stl_stats { stl_stats() { memset(&header, 0, 81); } - char header[81];// = ""; + char header[81]; stl_type type = (stl_type)0; uint32_t number_of_facets = 0; stl_vertex max = stl_vertex::Zero(); diff --git a/src/agg/agg_rasterizer_scanline_aa.h b/src/agg/agg_rasterizer_scanline_aa.h index ffc2ddf941..4925ca2092 100644 --- a/src/agg/agg_rasterizer_scanline_aa.h +++ b/src/agg/agg_rasterizer_scanline_aa.h @@ -156,7 +156,7 @@ namespace agg //------------------------------------------------------------------- template - void add_path(VertexSource& vs, unsigned path_id=0) + void add_path(VertexSource &&vs, unsigned path_id=0) { double x; double y; diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 587b814b24..6484da3d06 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -1,134 +1,31 @@ -cmake_minimum_required(VERSION 3.0) - -project(Libnest2D) - -if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - # Update if necessary - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ") -endif() - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED) - -# Add our own cmake module path. -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/) - -option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded - and the provided unit tests will be included in the build." OFF) - -option(LIBNEST2D_BUILD_EXAMPLES "If enabled, examples will be built." OFF) - -option(LIBNEST2D_HEADER_ONLY "If enabled static library will not be built." ON) - -set(GEOMETRY_BACKENDS clipper boost eigen) -set(LIBNEST2D_GEOMETRIES clipper CACHE STRING "Geometry backend") -set_property(CACHE LIBNEST2D_GEOMETRIES PROPERTY STRINGS ${GEOMETRY_BACKENDS}) -list(FIND GEOMETRY_BACKENDS ${LIBNEST2D_GEOMETRIES} GEOMETRY_TYPE) -if(${GEOMETRY_TYPE} EQUAL -1) - message(FATAL_ERROR "Option ${LIBNEST2D_GEOMETRIES} not supported, valid entries are ${GEOMETRY_BACKENDS}") -endif() - -set(OPTIMIZERS nlopt optimlib) -set(LIBNEST2D_OPTIMIZER nlopt CACHE STRING "Optimization backend") -set_property(CACHE LIBNEST2D_OPTIMIZER PROPERTY STRINGS ${OPTIMIZERS}) -list(FIND OPTIMIZERS ${LIBNEST2D_OPTIMIZER} OPTIMIZER_TYPE) -if(${OPTIMIZER_TYPE} EQUAL -1) - message(FATAL_ERROR "Option ${LIBNEST2D_OPTIMIZER} not supported, valid entries are ${OPTIMIZERS}") -endif() - -add_library(libnest2d INTERFACE) - -set(SRC_DIR ${PROJECT_SOURCE_DIR}/include) - set(LIBNEST2D_SRCFILES - ${SRC_DIR}/libnest2d/libnest2d.hpp # Templates only - ${SRC_DIR}/libnest2d/geometry_traits.hpp - ${SRC_DIR}/libnest2d/geometry_traits_nfp.hpp - ${SRC_DIR}/libnest2d/common.hpp - ${SRC_DIR}/libnest2d/optimizer.hpp - ${SRC_DIR}/libnest2d/utils/metaloop.hpp - ${SRC_DIR}/libnest2d/utils/rotfinder.hpp - ${SRC_DIR}/libnest2d/utils/rotcalipers.hpp - ${SRC_DIR}/libnest2d/utils/bigint.hpp - ${SRC_DIR}/libnest2d/utils/rational.hpp - ${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp - ${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp - ${SRC_DIR}/libnest2d/placers/nfpplacer.hpp - ${SRC_DIR}/libnest2d/selections/selection_boilerplate.hpp - ${SRC_DIR}/libnest2d/selections/filler.hpp - ${SRC_DIR}/libnest2d/selections/firstfit.hpp - ${SRC_DIR}/libnest2d/selections/djd_heuristic.hpp + include/libnest2d/libnest2d.hpp + include/libnest2d/nester.hpp + include/libnest2d/geometry_traits.hpp + include/libnest2d/geometry_traits_nfp.hpp + include/libnest2d/common.hpp + include/libnest2d/optimizer.hpp + include/libnest2d/utils/metaloop.hpp + include/libnest2d/utils/rotfinder.hpp + include/libnest2d/utils/rotcalipers.hpp + include/libnest2d/placers/placer_boilerplate.hpp + include/libnest2d/placers/bottomleftplacer.hpp + include/libnest2d/placers/nfpplacer.hpp + include/libnest2d/selections/selection_boilerplate.hpp + #include/libnest2d/selections/filler.hpp + include/libnest2d/selections/firstfit.hpp + #include/libnest2d/selections/djd_heuristic.hpp + include/libnest2d/backends/clipper/geometries.hpp + include/libnest2d/backends/clipper/clipper_polygon.hpp + include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp + include/libnest2d/optimizers/nlopt/simplex.hpp + include/libnest2d/optimizers/nlopt/subplex.hpp + include/libnest2d/optimizers/nlopt/genetic.hpp + src/libnest2d.cpp ) -set(TBB_STATIC ON) -find_package(TBB QUIET) -if(TBB_FOUND) - message(STATUS "Parallelization with Intel TBB") - target_include_directories(libnest2d INTERFACE ${TBB_INCLUDE_DIRS}) - target_compile_definitions(libnest2d INTERFACE ${TBB_DEFINITIONS} -DUSE_TBB) - if(MSVC) - # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. - target_compile_definitions(libnest2d INTERFACE -D__TBB_NO_IMPLICIT_LINKAGE) - endif() - # The Intel TBB library will use the std::exception_ptr feature of C++11. - target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) +add_library(libnest2d ${LIBNEST2D_SRCFILES}) - find_package(Threads REQUIRED) - target_link_libraries(libnest2d INTERFACE - tbb # VS debug mode needs linking this way: - # ${TBB_LIBRARIES} - ${CMAKE_DL_LIBS} - Threads::Threads - ) -else() - find_package(OpenMP QUIET) - - if(OpenMP_CXX_FOUND) - message(STATUS "Parallelization with OpenMP") - target_include_directories(libnest2d INTERFACE OpenMP::OpenMP_CXX) - target_link_libraries(libnest2d INTERFACE OpenMP::OpenMP_CXX) - else() - message("Parallelization with C++11 threads") - find_package(Threads REQUIRED) - target_link_libraries(libnest2d INTERFACE Threads::Threads) - endif() -endif() - -add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) -target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend) - -add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) -target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer) - -# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) -target_include_directories(libnest2d INTERFACE ${SRC_DIR}) - -if(NOT LIBNEST2D_HEADER_ONLY) - set(LIBNAME libnest2d_${LIBNEST2D_GEOMETRIES}_${LIBNEST2D_OPTIMIZER}) - add_library(${LIBNAME} ${PROJECT_SOURCE_DIR}/src/libnest2d.cpp) - target_link_libraries(${LIBNAME} PUBLIC libnest2d) - target_compile_definitions(${LIBNAME} PUBLIC LIBNEST2D_STATIC) -endif() - -if(LIBNEST2D_BUILD_EXAMPLES) - - add_executable(example examples/main.cpp - # tools/libnfpglue.hpp - # tools/libnfpglue.cpp - tools/nfp_svgnest.hpp - tools/nfp_svgnest_glue.hpp - tools/svgtools.hpp - tests/printer_parts.cpp - tests/printer_parts.h - ) - - if(NOT LIBNEST2D_HEADER_ONLY) - target_link_libraries(example ${LIBNAME}) - else() - target_link_libraries(example libnest2d) - endif() -endif() - -if(LIBNEST2D_UNITTESTS) - add_subdirectory(${PROJECT_SOURCE_DIR}/tests) -endif() +target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost) +target_compile_definitions(libnest2d PUBLIC USE_TBB LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_clipper) diff --git a/src/libnest2d/cmake_modules/DownloadNLopt.cmake b/src/libnest2d/cmake_modules/DownloadNLopt.cmake deleted file mode 100644 index 62b2b4c1a9..0000000000 --- a/src/libnest2d/cmake_modules/DownloadNLopt.cmake +++ /dev/null @@ -1,35 +0,0 @@ -include(DownloadProject) - -if (CMAKE_VERSION VERSION_LESS 3.2) - set(UPDATE_DISCONNECTED_IF_AVAILABLE "") -else() - set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") -endif() - -set(URL_NLOPT "https://github.com/stevengj/nlopt.git" - CACHE STRING "Location of the nlopt git repository") - -# set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt) -include(DownloadProject) -download_project( PROJ nlopt - GIT_REPOSITORY ${URL_NLOPT} - GIT_TAG v2.5.0 - # CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR} - ${UPDATE_DISCONNECTED_IF_AVAILABLE} -) - -set(SHARED_LIBS_STATE BUILD_SHARED_LIBS) -set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) -set(NLOPT_PYTHON OFF CACHE BOOL "" FORCE) -set(NLOPT_OCTAVE OFF CACHE BOOL "" FORCE) -set(NLOPT_MATLAB OFF CACHE BOOL "" FORCE) -set(NLOPT_GUILE OFF CACHE BOOL "" FORCE) -set(NLOPT_SWIG OFF CACHE BOOL "" FORCE) -set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE) - -add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR}) - -set(NLopt_LIBS nlopt) -set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR} - ${nlopt_BINARY_DIR}/src/api) -set(SHARED_LIBS_STATE ${SHARED_STATE}) \ No newline at end of file diff --git a/src/libnest2d/cmake_modules/DownloadProject.CMakeLists.cmake.in b/src/libnest2d/cmake_modules/DownloadProject.CMakeLists.cmake.in deleted file mode 100644 index d5cf3c1d93..0000000000 --- a/src/libnest2d/cmake_modules/DownloadProject.CMakeLists.cmake.in +++ /dev/null @@ -1,17 +0,0 @@ -# Distributed under the OSI-approved MIT License. See accompanying -# file LICENSE or https://github.com/Crascit/DownloadProject for details. - -cmake_minimum_required(VERSION 2.8.2) - -project(${DL_ARGS_PROJ}-download NONE) - -include(ExternalProject) -ExternalProject_Add(${DL_ARGS_PROJ}-download - ${DL_ARGS_UNPARSED_ARGUMENTS} - SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" - BINARY_DIR "${DL_ARGS_BINARY_DIR}" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) \ No newline at end of file diff --git a/src/libnest2d/cmake_modules/DownloadProject.cmake b/src/libnest2d/cmake_modules/DownloadProject.cmake deleted file mode 100644 index 1709e09adc..0000000000 --- a/src/libnest2d/cmake_modules/DownloadProject.cmake +++ /dev/null @@ -1,182 +0,0 @@ -# Distributed under the OSI-approved MIT License. See accompanying -# file LICENSE or https://github.com/Crascit/DownloadProject for details. -# -# MODULE: DownloadProject -# -# PROVIDES: -# download_project( PROJ projectName -# [PREFIX prefixDir] -# [DOWNLOAD_DIR downloadDir] -# [SOURCE_DIR srcDir] -# [BINARY_DIR binDir] -# [QUIET] -# ... -# ) -# -# Provides the ability to download and unpack a tarball, zip file, git repository, -# etc. at configure time (i.e. when the cmake command is run). How the downloaded -# and unpacked contents are used is up to the caller, but the motivating case is -# to download source code which can then be included directly in the build with -# add_subdirectory() after the call to download_project(). Source and build -# directories are set up with this in mind. -# -# The PROJ argument is required. The projectName value will be used to construct -# the following variables upon exit (obviously replace projectName with its actual -# value): -# -# projectName_SOURCE_DIR -# projectName_BINARY_DIR -# -# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically -# need to be provided. They can be specified if you want the downloaded source -# and build directories to be located in a specific place. The contents of -# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the -# locations used whether you provide SOURCE_DIR/BINARY_DIR or not. -# -# The DOWNLOAD_DIR argument does not normally need to be set. It controls the -# location of the temporary CMake build used to perform the download. -# -# The PREFIX argument can be provided to change the base location of the default -# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments -# are provided, then PREFIX will have no effect. The default value for PREFIX is -# CMAKE_BINARY_DIR. -# -# The QUIET option can be given if you do not want to show the output associated -# with downloading the specified project. -# -# In addition to the above, any other options are passed through unmodified to -# ExternalProject_Add() to perform the actual download, patch and update steps. -# The following ExternalProject_Add() options are explicitly prohibited (they -# are reserved for use by the download_project() command): -# -# CONFIGURE_COMMAND -# BUILD_COMMAND -# INSTALL_COMMAND -# TEST_COMMAND -# -# Only those ExternalProject_Add() arguments which relate to downloading, patching -# and updating of the project sources are intended to be used. Also note that at -# least one set of download-related arguments are required. -# -# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to -# prevent a check at the remote end for changes every time CMake is run -# after the first successful download. See the documentation of the ExternalProject -# module for more information. It is likely you will want to use this option if it -# is available to you. Note, however, that the ExternalProject implementation contains -# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when -# using the URL download method or when specifying a SOURCE_DIR with no download -# method. Fixes for these have been created, the last of which is scheduled for -# inclusion in CMake 3.8.0. Details can be found here: -# -# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c -# https://gitlab.kitware.com/cmake/cmake/issues/16428 -# -# If you experience build errors related to the update step, consider avoiding -# the use of UPDATE_DISCONNECTED. -# -# EXAMPLE USAGE: -# -# include(DownloadProject) -# download_project(PROJ googletest -# GIT_REPOSITORY https://github.com/google/googletest.git -# GIT_TAG master -# UPDATE_DISCONNECTED 1 -# QUIET -# ) -# -# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) -# -#======================================================================================== - - -set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") - -include(CMakeParseArguments) - -function(download_project) - - set(options QUIET) - set(oneValueArgs - PROJ - PREFIX - DOWNLOAD_DIR - SOURCE_DIR - BINARY_DIR - # Prevent the following from being passed through - CONFIGURE_COMMAND - BUILD_COMMAND - INSTALL_COMMAND - TEST_COMMAND - ) - set(multiValueArgs "") - - cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # Hide output if requested - if (DL_ARGS_QUIET) - set(OUTPUT_QUIET "OUTPUT_QUIET") - else() - unset(OUTPUT_QUIET) - message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") - endif() - - # Set up where we will put our temporary CMakeLists.txt file and also - # the base point below which the default source and binary dirs will be. - # The prefix must always be an absolute path. - if (NOT DL_ARGS_PREFIX) - set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") - else() - get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE - BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") - endif() - if (NOT DL_ARGS_DOWNLOAD_DIR) - set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") - endif() - - # Ensure the caller can know where to find the source and build directories - if (NOT DL_ARGS_SOURCE_DIR) - set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") - endif() - if (NOT DL_ARGS_BINARY_DIR) - set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") - endif() - set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) - set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) - - # The way that CLion manages multiple configurations, it causes a copy of - # the CMakeCache.txt to be copied across due to it not expecting there to - # be a project within a project. This causes the hard-coded paths in the - # cache to be copied and builds to fail. To mitigate this, we simply - # remove the cache if it exists before we configure the new project. It - # is safe to do so because it will be re-generated. Since this is only - # executed at the configure step, it should not cause additional builds or - # downloads. - file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") - - # Create and build a separate CMake project to carry out the download. - # If we've already previously done these steps, they will not cause - # anything to be updated, so extra rebuilds of the project won't occur. - # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project - # has this set to something not findable on the PATH. - configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" - "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" - -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" - . - RESULT_VARIABLE result - ${OUTPUT_QUIET} - WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" - ) - if(result) - message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") - endif() - execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - ${OUTPUT_QUIET} - WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" - ) - if(result) - message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") - endif() - -endfunction() \ No newline at end of file diff --git a/src/libnest2d/cmake_modules/FindClipper.cmake b/src/libnest2d/cmake_modules/FindClipper.cmake deleted file mode 100644 index 01b6b99d59..0000000000 --- a/src/libnest2d/cmake_modules/FindClipper.cmake +++ /dev/null @@ -1,58 +0,0 @@ -# Find Clipper library (http://www.angusj.com/delphi/clipper.php). -# The following variables are set -# -# CLIPPER_FOUND -# CLIPPER_INCLUDE_DIRS -# CLIPPER_LIBRARIES -# -# It searches the environment variable $CLIPPER_PATH automatically. - -FIND_PATH(CLIPPER_INCLUDE_DIRS clipper.hpp - $ENV{CLIPPER_PATH} - $ENV{CLIPPER_PATH}/cpp/ - $ENV{CLIPPER_PATH}/include/ - $ENV{CLIPPER_PATH}/include/polyclipping/ - ${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/ - ${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/polyclipping/ - ${CMAKE_PREFIX_PATH}/include/polyclipping - ${CMAKE_PREFIX_PATH}/include/ - /opt/local/include/ - /opt/local/include/polyclipping/ - /usr/local/include/ - /usr/local/include/polyclipping/ - /usr/include - /usr/include/polyclipping/) - -FIND_LIBRARY(CLIPPER_LIBRARIES polyclipping - $ENV{CLIPPER_PATH} - $ENV{CLIPPER_PATH}/cpp/ - $ENV{CLIPPER_PATH}/cpp/build/ - $ENV{CLIPPER_PATH}/lib/ - $ENV{CLIPPER_PATH}/lib/polyclipping/ - ${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/ - ${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/polyclipping/ - ${CMAKE_PREFIX_PATH}/lib/ - ${CMAKE_PREFIX_PATH}/lib/polyclipping/ - /opt/local/lib/ - /opt/local/lib/polyclipping/ - /usr/local/lib/ - /usr/local/lib/polyclipping/ - /usr/lib/polyclipping) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper - "Clipper library cannot be found. Consider set CLIPPER_PATH environment variable" - CLIPPER_INCLUDE_DIRS - CLIPPER_LIBRARIES) - -MARK_AS_ADVANCED( - CLIPPER_INCLUDE_DIRS - CLIPPER_LIBRARIES) - -if(CLIPPER_FOUND) - add_library(Clipper::Clipper INTERFACE IMPORTED) - set_target_properties(Clipper::Clipper PROPERTIES INTERFACE_LINK_LIBRARIES ${CLIPPER_LIBRARIES}) - set_target_properties(Clipper::Clipper PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CLIPPER_INCLUDE_DIRS}) - #target_link_libraries(Clipper::Clipper INTERFACE ${CLIPPER_LIBRARIES}) - #target_include_directories(Clipper::Clipper INTERFACE ${CLIPPER_INCLUDE_DIRS}) -endif() diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h deleted file mode 100644 index 4661b45744..0000000000 --- a/src/libnest2d/include/libnest2d.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef LIBNEST2D_H -#define LIBNEST2D_H - -// The type of backend should be set conditionally by the cmake configuriation -// for now we set it statically to clipper backend -#ifdef LIBNEST2D_BACKEND_CLIPPER -#include -#endif - -#ifdef LIBNEST2D_OPTIMIZER_NLOPT -// We include the stock optimizers for local and global optimization -#include // Local subplex for NfpPlacer -#include // Genetic for min. bounding box -#endif - -#include -#include -#include -#include -#include -#include - -namespace libnest2d { - -using Point = PointImpl; -using Coord = TCoord; -using Box = _Box; -using Segment = _Segment; -using Circle = _Circle; - -using Item = _Item; -using Rectangle = _Rectangle; -using PackGroup = _PackGroup; - -using FillerSelection = selections::_FillerSelection; -using FirstFitSelection = selections::_FirstFitSelection; -using DJDHeuristic = selections::_DJDHeuristic; - -template // Generic placer for arbitrary bin types -using _NfpPlacer = placers::_NofitPolyPlacer; - -// NfpPlacer is with Box bin -using NfpPlacer = _NfpPlacer; - -// This supports only box shaped bins -using BottomLeftPlacer = placers::_BottomLeftPlacer; - -#ifdef LIBNEST2D_STATIC - -extern template class Nester; -extern template class Nester; -extern template PackGroup Nester::execute( - std::vector::iterator, std::vector::iterator); -extern template PackGroup Nester::execute( - std::vector::iterator, std::vector::iterator); - -#endif - -template::iterator> -void nest(Iterator from, Iterator to, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - _Nester nester(bin, dist, pconf, sconf); - nester.execute(from, to); -} - -template::iterator> -void nest(Iterator from, Iterator to, - const typename Placer::BinType& bin, - ProgressFunction prg, - StopCondition scond = []() { return false; }, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - _Nester nester(bin, dist, pconf, sconf); - if(prg) nester.progressIndicator(prg); - if(scond) nester.stopCondition(scond); - nester.execute(from, to); -} - -#ifdef LIBNEST2D_STATIC - -extern template class Nester; -extern template class Nester; - -extern template void nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); - -extern template void nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); - -#endif - -template> -void nest(Container&& cont, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - nest(cont.begin(), cont.end(), bin, dist, pconf, sconf); -} - -template> -void nest(Container&& cont, - const typename Placer::BinType& bin, - ProgressFunction prg, - StopCondition scond = []() { return false; }, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - nest(cont.begin(), cont.end(), bin, prg, scond, dist, - pconf, sconf); -} - -} - -#endif // LIBNEST2D_H diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt deleted file mode 100644 index 2020893567..0000000000 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ /dev/null @@ -1,73 +0,0 @@ -if(NOT TARGET clipper) # If there is a clipper target in the parent project we are good to go. - - find_package(Clipper 6.1) - - if(NOT CLIPPER_FOUND) - find_package(Subversion QUIET) - if(Subversion_FOUND) - - set(URL_CLIPPER "https://svn.code.sf.net/p/polyclipping/code/trunk/cpp" - CACHE STRING "Clipper source code repository location.") - - message(STATUS "Clipper not found so it will be downloaded.") - # Silently download and build the library in the build dir - - if (CMAKE_VERSION VERSION_LESS 3.2) - set(UPDATE_DISCONNECTED_IF_AVAILABLE "") - else() - set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") - endif() - - include(DownloadProject) - download_project( PROJ clipper_library - SVN_REPOSITORY ${URL_CLIPPER} - SVN_REVISION -r540 - #SOURCE_SUBDIR cpp - INSTALL_COMMAND "" - CONFIGURE_COMMAND "" # Not working, I will just add the source files - ${UPDATE_DISCONNECTED_IF_AVAILABLE} - ) - - # This is not working and I dont have time to fix it - # add_subdirectory(${clipper_library_SOURCE_DIR}/cpp - # ${clipper_library_BINARY_DIR} - # ) - - add_library(clipperBackend STATIC - ${clipper_library_SOURCE_DIR}/clipper.cpp - ${clipper_library_SOURCE_DIR}/clipper.hpp) - - target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR}) - else() - message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. - You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") - endif() - else() - add_library(clipperBackend INTERFACE) - target_link_libraries(clipperBackend INTERFACE Clipper::Clipper) - endif() -else() - # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) - # set(CLIPPER_LIBRARIES clipper PARENT_SCOPE) - add_library(clipperBackend INTERFACE) - target_link_libraries(clipperBackend INTERFACE clipper) -endif() - -# Clipper backend is not enough on its own, it still needs some functions -# from Boost geometry -if(NOT Boost_FOUND) - find_package(Boost 1.58 REQUIRED) - # TODO automatic download of boost geometry headers -endif() - -target_link_libraries(clipperBackend INTERFACE Boost::boost ) -#target_sources(ClipperBackend INTERFACE -# ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp -# ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp -# ${SRC_DIR}/libnest2d/utils/boost_alg.hpp ) - -target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) - -# And finally plug the clipperBackend into libnest2d -# target_link_libraries(libnest2d INTERFACE clipperBackend) - diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index 4a2c69bca2..29a1ccd047 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -299,9 +299,456 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, template NfpResult nfpSimpleSimple(const RawShape& cstationary, - const RawShape& cother) + const RawShape& cother) { - return {}; + + // Algorithms are from the original algorithm proposed in paper: + // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 1: Obtaining the minkowski sum + // ///////////////////////////////////////////////////////////////////////// + + // I guess this is not a full minkowski sum of the two input polygons by + // definition. This yields a subset that is compatible with the next 2 + // algorithms. + + using Result = NfpResult; + using Vertex = TPoint; + using Coord = TCoord; + using Edge = _Segment; + namespace sl = shapelike; + using std::signbit; + using std::sort; + using std::vector; + using std::ref; + using std::reference_wrapper; + + // TODO The original algorithms expects the stationary polygon in + // counter clockwise and the orbiter in clockwise order. + // So for preventing any further complication, I will make the input + // the way it should be, than make my way around the orientations. + + // Reverse the stationary contour to counter clockwise + auto stcont = sl::contour(cstationary); + { + std::reverse(sl::begin(stcont), sl::end(stcont)); + stcont.pop_back(); + auto it = std::min_element(sl::begin(stcont), sl::end(stcont), + [](const Vertex& v1, const Vertex& v2) { + return getY(v1) < getY(v2); + }); + std::rotate(sl::begin(stcont), it, sl::end(stcont)); + sl::addVertex(stcont, sl::front(stcont)); + } + RawShape stationary; + sl::contour(stationary) = stcont; + + // Reverse the orbiter contour to counter clockwise + auto orbcont = sl::contour(cother); + { + std::reverse(orbcont.begin(), orbcont.end()); + + // Step 1: Make the orbiter reverse oriented + + orbcont.pop_back(); + auto it = std::min_element(orbcont.begin(), orbcont.end(), + [](const Vertex& v1, const Vertex& v2) { + return getY(v1) < getY(v2); + }); + + std::rotate(orbcont.begin(), it, orbcont.end()); + orbcont.emplace_back(orbcont.front()); + + for(auto &v : orbcont) v = -v; + + } + + // Copy the orbiter (contour only), we will have to work on it + RawShape orbiter; + sl::contour(orbiter) = orbcont; + + // An edge with additional data for marking it + struct MarkedEdge { + Edge e; Radians turn_angle = 0; bool is_turning_point = false; + MarkedEdge() = default; + MarkedEdge(const Edge& ed, Radians ta, bool tp): + e(ed), turn_angle(ta), is_turning_point(tp) {} + + // debug + std::string label; + }; + + // Container for marked edges + using EdgeList = vector; + + EdgeList A, B; + + // This is how an edge list is created from the polygons + auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { + auto& poly = sl::contour(ppoly); + + L.reserve(sl::contourVertexCount(poly)); + + if(dir > 0) { + auto it = poly.begin(); + auto nextit = std::next(it); + + double turn_angle = 0; + bool is_turn_point = false; + + while(nextit != poly.end()) { + L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); + it++; nextit++; + } + } else { + auto it = sl::rbegin(poly); + auto nextit = std::next(it); + + double turn_angle = 0; + bool is_turn_point = false; + + while(nextit != sl::rend(poly)) { + L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); + it++; nextit++; + } + } + + auto getTurnAngle = [](const Edge& e1, const Edge& e2) { + auto phi = e1.angleToXaxis(); + auto phi_prev = e2.angleToXaxis(); + auto turn_angle = phi-phi_prev; + if(turn_angle > Pi) turn_angle -= TwoPi; + if(turn_angle < -Pi) turn_angle += TwoPi; + return turn_angle; + }; + + auto eit = L.begin(); + auto enext = std::next(eit); + + eit->turn_angle = getTurnAngle(L.front().e, L.back().e); + + while(enext != L.end()) { + enext->turn_angle = getTurnAngle( enext->e, eit->e); + eit->is_turning_point = + signbit(enext->turn_angle) != signbit(eit->turn_angle); + ++eit; ++enext; + } + + L.back().is_turning_point = signbit(L.back().turn_angle) != + signbit(L.front().turn_angle); + + }; + + // Step 2: Fill the edgelists + fillEdgeList(A, stationary, 1); + fillEdgeList(B, orbiter, 1); + + int i = 1; + for(MarkedEdge& me : A) { + std::cout << "a" << i << ":\n\t" + << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" + << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" + << "Turning point: " << (me.is_turning_point ? "yes" : "no") + << std::endl; + + me.label = "a"; me.label += std::to_string(i); + i++; + } + + i = 1; + for(MarkedEdge& me : B) { + std::cout << "b" << i << ":\n\t" + << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" + << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" + << "Turning point: " << (me.is_turning_point ? "yes" : "no") + << std::endl; + me.label = "b"; me.label += std::to_string(i); + i++; + } + + // A reference to a marked edge that also knows its container + struct MarkedEdgeRef { + reference_wrapper eref; + reference_wrapper> container; + Coord dir = 1; // Direction modifier + + inline Radians angleX() const { return eref.get().e.angleToXaxis(); } + inline const Edge& edge() const { return eref.get().e; } + inline Edge& edge() { return eref.get().e; } + inline bool isTurningPoint() const { + return eref.get().is_turning_point; + } + inline bool isFrom(const vector& cont ) { + return &(container.get()) == &cont; + } + inline bool eq(const MarkedEdgeRef& mr) { + return &(eref.get()) == &(mr.eref.get()); + } + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec): + eref(er), container(ec), dir(1) {} + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec, + Coord d): + eref(er), container(ec), dir(d) {} + }; + + using EdgeRefList = vector; + + // Comparing two marked edges + auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { + return e1.angleX() < e2.angleX(); + }; + + EdgeRefList Aref, Bref; // We create containers for the references + Aref.reserve(A.size()); Bref.reserve(B.size()); + + // Fill reference container for the stationary polygon + std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { + Aref.emplace_back( ref(me), ref(Aref) ); + }); + + // Fill reference container for the orbiting polygon + std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { + Bref.emplace_back( ref(me), ref(Bref) ); + }); + + auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure + (const EdgeRefList& Q, const EdgeRefList& R, bool positive) + { + + // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" + // Sort the containers of edge references and merge them. + // Q could be sorted only once and be reused here but we would still + // need to merge it with sorted(R). + + EdgeRefList merged; + EdgeRefList S, seq; + merged.reserve(Q.size() + R.size()); + + merged.insert(merged.end(), R.begin(), R.end()); + std::stable_sort(merged.begin(), merged.end(), sortfn); + merged.insert(merged.end(), Q.begin(), Q.end()); + std::stable_sort(merged.begin(), merged.end(), sortfn); + + // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" + // we don't use i, instead, q is an iterator into Q. k would be an index + // into the merged sequence but we use "it" as an iterator for that + + // here we obtain references for the containers for later comparisons + const auto& Rcont = R.begin()->container.get(); + const auto& Qcont = Q.begin()->container.get(); + + // Set the initial direction + Coord dir = 1; + + // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q; + if(positive) { + auto q = Q.begin(); + S.emplace_back(*q); + + // Roughly step 3 + + std::cout << "merged size: " << merged.size() << std::endl; + auto mit = merged.begin(); + for(bool finish = false; !finish && q != Q.end();) { + ++q; // "Set i = i + 1" + + while(!finish && mit != merged.end()) { + if(mit->isFrom(Rcont)) { + auto s = *mit; + s.dir = dir; + S.emplace_back(s); + } + + if(mit->eq(*q)) { + S.emplace_back(*q); + if(mit->isTurningPoint()) dir = -dir; + if(q == Q.begin()) finish = true; + break; + } + + mit += dir; + // __nfp::advance(mit, merged, dir > 0); + } + } + } else { + auto q = Q.rbegin(); + S.emplace_back(*q); + + // Roughly step 3 + + std::cout << "merged size: " << merged.size() << std::endl; + auto mit = merged.begin(); + for(bool finish = false; !finish && q != Q.rend();) { + ++q; // "Set i = i + 1" + + while(!finish && mit != merged.end()) { + if(mit->isFrom(Rcont)) { + auto s = *mit; + s.dir = dir; + S.emplace_back(s); + } + + if(mit->eq(*q)) { + S.emplace_back(*q); + S.back().dir = -1; + if(mit->isTurningPoint()) dir = -dir; + if(q == Q.rbegin()) finish = true; + break; + } + + mit += dir; + // __nfp::advance(mit, merged, dir > 0); + } + } + } + + + // Step 4: + + // "Let starting edge r1 be in position si in sequence" + // whaaat? I guess this means the following: + auto it = S.begin(); + while(!it->eq(*R.begin())) ++it; + + // "Set j = 1, next = 2, direction = 1, seq1 = si" + // we don't use j, seq is expanded dynamically. + dir = 1; + auto next = std::next(R.begin()); seq.emplace_back(*it); + + // Step 5: + // "If all si edges have been allocated to seqj" should mean that + // we loop until seq has equal size with S + auto send = it; //it == S.begin() ? it : std::prev(it); + while(it != S.end()) { + ++it; if(it == S.end()) it = S.begin(); + if(it == send) break; + + if(it->isFrom(Qcont)) { + seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si" + + // "If si is a turning point in Q, + // direction = - direction, next = next + direction" + if(it->isTurningPoint()) { + dir = -dir; + next += dir; +// __nfp::advance(next, R, dir > 0); + } + } + + if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext" + // "j = j + 1, seqj = si, next = next + direction" + seq.emplace_back(*it); + next += dir; +// __nfp::advance(next, R, dir > 0); + } + } + + return seq; + }; + + std::vector seqlist; + seqlist.reserve(Bref.size()); + + EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram + + // make the slope diagram of B + std::sort(Bslope.begin(), Bslope.end(), sortfn); + + auto slopeit = Bslope.begin(); // search for the first turning point + while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; + + if(slopeit == Bslope.end()) { + // no turning point means convex polygon. + seqlist.emplace_back(mink(Aref, Bref, true)); + } else { + int dir = 1; + + auto firstturn = Bref.begin(); + while(!firstturn->eq(*slopeit)) ++firstturn; + + assert(firstturn != Bref.end()); + + EdgeRefList bgroup; bgroup.reserve(Bref.size()); + bgroup.emplace_back(*slopeit); + + auto b_it = std::next(firstturn); + while(b_it != firstturn) { + if(b_it == Bref.end()) b_it = Bref.begin(); + + while(!slopeit->eq(*b_it)) { + __nfp::advance(slopeit, Bslope, dir > 0); + } + + if(!slopeit->isTurningPoint()) { + bgroup.emplace_back(*slopeit); + } else { + if(!bgroup.empty()) { + if(dir > 0) bgroup.emplace_back(*slopeit); + for(auto& me : bgroup) { + std::cout << me.eref.get().label << ", "; + } + std::cout << std::endl; + seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); + bgroup.clear(); + if(dir < 0) bgroup.emplace_back(*slopeit); + } else { + bgroup.emplace_back(*slopeit); + } + + dir *= -1; + } + ++b_it; + } + } + +// while(it != Bref.end()) // This is step 3 and step 4 in one loop +// if(it->isTurningPoint()) { +// R = {R.last, it++}; +// auto seq = mink(Q, R, orientation); + +// // TODO step 6 (should be 5 shouldn't it?): linking edges from A +// // I don't get this step + +// seqlist.insert(seqlist.end(), seq.begin(), seq.end()); +// orientation = !orientation; +// } else ++it; + +// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 2: breaking Minkowski sums into track line trips + // ///////////////////////////////////////////////////////////////////////// + + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 3: finding the boundary of the NFP from track line trips + // ///////////////////////////////////////////////////////////////////////// + + + for(auto& seq : seqlist) { + std::cout << "seqlist size: " << seq.size() << std::endl; + for(auto& s : seq) { + std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; + } + std::cout << std::endl; + } + + auto& seq = seqlist.front(); + RawShape rsh; + Vertex top_nfp; + std::vector edgelist; edgelist.reserve(seq.size()); + for(auto& s : seq) { + edgelist.emplace_back(s.eref.get().e); + } + + __nfp::buildPolygon(edgelist, rsh, top_nfp); + + return Result(rsh, top_nfp); } // Specializable NFP implementation class. Specialize it if you have a faster diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 29d52c10f8..b6d7fcdcf9 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -1,862 +1,134 @@ #ifndef LIBNEST2D_HPP #define LIBNEST2D_HPP -#include -#include -#include -#include -#include -#include +// The type of backend should be set conditionally by the cmake configuriation +// for now we set it statically to clipper backend +#ifdef LIBNEST2D_GEOMETRIES_clipper +#include +#endif -#include +#ifdef LIBNEST2D_OPTIMIZER_nlopt +// We include the stock optimizers for local and global optimization +#include // Local subplex for NfpPlacer +#include // Genetic for min. bounding box +#endif + +#include +#include +#include +#include +#include +#include namespace libnest2d { -static const constexpr int BIN_ID_UNSET = -1; +using Point = PointImpl; +using Coord = TCoord; +using Box = _Box; +using Segment = _Segment; +using Circle = _Circle; -/** - * \brief An item to be placed on a bin. - * - * It holds a copy of the original shape object but supports move construction - * from the shape objects if its an rvalue reference. This way we can construct - * the items without the cost of copying a potentially large amount of input. - * - * The results of some calculations are cached for maintaining fast run times. - * For this reason, memory demands are much higher but this should pay off. - */ -template -class _Item { - using Coord = TCoord>; - using Vertex = TPoint; - using Box = _Box; +using Item = _Item; +using Rectangle = _Rectangle; +using PackGroup = _PackGroup; - using VertexConstIterator = typename TContour::const_iterator; +using FillerSelection = selections::_FillerSelection; +using FirstFitSelection = selections::_FirstFitSelection; +using DJDHeuristic = selections::_DJDHeuristic; - // The original shape that gets encapsulated. - RawShape sh_; +template // Generic placer for arbitrary bin types +using _NfpPlacer = placers::_NofitPolyPlacer; - // Transformation data - Vertex translation_{0, 0}; - Radians rotation_{0.0}; - Coord inflation_{0}; +// NfpPlacer is with Box bin +using NfpPlacer = _NfpPlacer; - // Info about whether the transformations will have to take place - // This is needed because if floating point is used, it is hard to say - // that a zero angle is not a rotation because of testing for equality. - bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; +// This supports only box shaped bins +using BottomLeftPlacer = placers::_BottomLeftPlacer; - // For caching the calculations as they can get pretty expensive. - mutable RawShape tr_cache_; - mutable bool tr_cache_valid_ = false; - mutable double area_cache_ = 0; - mutable bool area_cache_valid_ = false; - mutable RawShape inflate_cache_; - mutable bool inflate_cache_valid_ = false; +#ifdef LIBNEST2D_STATIC - enum class Convexity: char { - UNCHECKED, - C_TRUE, - C_FALSE - }; +extern template class _Nester; +extern template class _Nester; +extern template std::size_t _Nester::execute( + std::vector::iterator, std::vector::iterator); +extern template std::size_t _Nester::execute( + std::vector::iterator, std::vector::iterator); - mutable Convexity convexity_ = Convexity::UNCHECKED; - mutable VertexConstIterator rmt_; // rightmost top vertex - mutable VertexConstIterator lmb_; // leftmost bottom vertex - mutable bool rmt_valid_ = false, lmb_valid_ = false; - mutable struct BBCache { - Box bb; bool valid; - BBCache(): valid(false) {} - } bb_cache_; +#endif + +template +struct NestConfig { + typename Placer::Config placer_config; + typename Selector::Config selector_config; + using Placement = typename Placer::Config; + using Selection = typename Selector::Config; - int binid_{BIN_ID_UNSET}, priority_{0}; - bool fixed_{false}; - -public: - - /// The type of the shape which was handed over as the template argument. - using ShapeType = RawShape; - - /** - * \brief Iterator type for the outer vertices. - * - * Only const iterators can be used. The _Item type is not intended to - * modify the carried shapes from the outside. The main purpose of this type - * is to cache the calculation results from the various operators it - * supports. Giving out a non const iterator would make it impossible to - * perform correct cache invalidation. - */ - using Iterator = VertexConstIterator; - - /** - * @brief Get the orientation of the polygon. - * - * The orientation have to be specified as a specialization of the - * OrientationType struct which has a Value constant. - * - * @return The orientation type identifier for the _Item type. - */ - static BP2D_CONSTEXPR Orientation orientation() { - return OrientationType::Value; - } - - /** - * @brief Constructing an _Item form an existing raw shape. The shape will - * be copied into the _Item object. - * @param sh The original shape object. - */ - explicit inline _Item(const RawShape& sh): sh_(sh) {} - - /** - * @brief Construction of an item by moving the content of the raw shape, - * assuming that it supports move semantics. - * @param sh The original shape object. - */ - explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {} - - /** - * @brief Create an item from an initializer list. - * @param il The initializer list of vertices. - */ - inline _Item(const std::initializer_list< Vertex >& il): - sh_(sl::create(il)) {} - - inline _Item(const TContour& contour, - const THolesContainer& holes = {}): - sh_(sl::create(contour, holes)) {} - - inline _Item(TContour&& contour, - THolesContainer&& holes): - sh_(sl::create(std::move(contour), std::move(holes))) {} - - inline bool isFixed() const noexcept { return fixed_; } - inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } - - inline void binId(int idx) { binid_ = idx; } - inline int binId() const noexcept { return binid_; } - - inline void priority(int p) { priority_ = p; } - inline int priority() const noexcept { return priority_; } - - /** - * @brief Convert the polygon to string representation. The format depends - * on the implementation of the polygon. - * @return - */ - inline std::string toString() const - { - return sl::toString(sh_); - } - - /// Iterator tho the first contour vertex in the polygon. - inline Iterator begin() const - { - return sl::cbegin(sh_); - } - - /// Alias to begin() - inline Iterator cbegin() const - { - return sl::cbegin(sh_); - } - - /// Iterator to the last contour vertex. - inline Iterator end() const - { - return sl::cend(sh_); - } - - /// Alias to end() - inline Iterator cend() const - { - return sl::cend(sh_); - } - - /** - * @brief Get a copy of an outer vertex within the carried shape. - * - * Note that the vertex considered here is taken from the original shape - * that this item is constructed from. This means that no transformation is - * applied to the shape in this call. - * - * @param idx The index of the requested vertex. - * @return A copy of the requested vertex. - */ - inline Vertex vertex(unsigned long idx) const - { - return sl::vertex(sh_, idx); - } - - /** - * @brief Modify a vertex. - * - * Note that this method will invalidate every cached calculation result - * including polygon offset and transformations. - * - * @param idx The index of the requested vertex. - * @param v The new vertex data. - */ - inline void setVertex(unsigned long idx, const Vertex& v ) - { - invalidateCache(); - sl::vertex(sh_, idx) = v; - } - - /** - * @brief Calculate the shape area. - * - * The method returns absolute value and does not reflect polygon - * orientation. The result is cached, subsequent calls will have very little - * cost. - * @return The shape area in floating point double precision. - */ - inline double area() const { - double ret ; - if(area_cache_valid_) ret = area_cache_; - else { - ret = sl::area(infaltedShape()); - area_cache_ = ret; - area_cache_valid_ = true; - } - return ret; - } - - inline bool isContourConvex() const { - bool ret = false; - - switch(convexity_) { - case Convexity::UNCHECKED: - ret = sl::isConvex(sl::contour(transformedShape())); - convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE; - break; - case Convexity::C_TRUE: ret = true; break; - case Convexity::C_FALSE:; - } - - return ret; - } - - inline bool isHoleConvex(unsigned /*holeidx*/) const { - return false; - } - - inline bool areHolesConvex() const { - return false; - } - - /// The number of the outer ring vertices. - inline size_t vertexCount() const { - return sl::contourVertexCount(sh_); - } - - inline size_t holeCount() const { - return sl::holeCount(sh_); - } - - /** - * @brief isPointInside - * @param p - * @return - */ - inline bool isInside(const Vertex& p) const - { - return sl::isInside(p, transformedShape()); - } - - inline bool isInside(const _Item& sh) const - { - return sl::isInside(transformedShape(), sh.transformedShape()); - } - - inline bool isInside(const RawShape& sh) const - { - return sl::isInside(transformedShape(), sh); - } - - inline bool isInside(const _Box>& box) const; - inline bool isInside(const _Circle>& box) const; - - inline void translate(const Vertex& d) BP2D_NOEXCEPT - { - translation(translation() + d); - } - - inline void rotate(const Radians& rads) BP2D_NOEXCEPT - { - rotation(rotation() + rads); - } - - inline void inflation(Coord distance) BP2D_NOEXCEPT - { - inflation_ = distance; - has_inflation_ = true; - invalidateCache(); - } - - inline Coord inflation() const BP2D_NOEXCEPT { - return inflation_; - } - - inline void inflate(Coord distance) BP2D_NOEXCEPT - { - inflation(inflation() + distance); - } - - inline Radians rotation() const BP2D_NOEXCEPT - { - return rotation_; - } - - inline TPoint translation() const BP2D_NOEXCEPT - { - return translation_; - } - - inline void rotation(Radians rot) BP2D_NOEXCEPT - { - if(rotation_ != rot) { - rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false; - rmt_valid_ = false; lmb_valid_ = false; - bb_cache_.valid = false; - } - } - - inline void translation(const TPoint& tr) BP2D_NOEXCEPT - { - if(translation_ != tr) { - translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; - //bb_cache_.valid = false; - } - } - - inline const RawShape& transformedShape() const - { - if(tr_cache_valid_) return tr_cache_; - - RawShape cpy = infaltedShape(); - if(has_rotation_) sl::rotate(cpy, rotation_); - if(has_translation_) sl::translate(cpy, translation_); - tr_cache_ = cpy; tr_cache_valid_ = true; - rmt_valid_ = false; lmb_valid_ = false; - - return tr_cache_; - } - - inline operator RawShape() const - { - return transformedShape(); - } - - inline const RawShape& rawShape() const BP2D_NOEXCEPT - { - return sh_; - } - - inline void resetTransformation() BP2D_NOEXCEPT - { - has_translation_ = false; has_rotation_ = false; has_inflation_ = false; - invalidateCache(); - } - - inline Box boundingBox() const { - if(!bb_cache_.valid) { - if(!has_rotation_) - bb_cache_.bb = sl::boundingBox(infaltedShape()); - else { - // TODO make sure this works - auto rotsh = infaltedShape(); - sl::rotate(rotsh, rotation_); - bb_cache_.bb = sl::boundingBox(rotsh); - } - bb_cache_.valid = true; - } - - auto &bb = bb_cache_.bb; auto &tr = translation_; - return {bb.minCorner() + tr, bb.maxCorner() + tr }; - } - - inline Vertex referenceVertex() const { - return rightmostTopVertex(); - } - - inline Vertex rightmostTopVertex() const { - if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex - auto& tsh = transformedShape(); - rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort); - rmt_valid_ = true; - } - return *rmt_; - } - - inline Vertex leftmostBottomVertex() const { - if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex - auto& tsh = transformedShape(); - lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort); - lmb_valid_ = true; - } - return *lmb_; - } - - //Static methods: - - inline static bool intersects(const _Item& sh1, const _Item& sh2) - { - return sl::intersects(sh1.transformedShape(), - sh2.transformedShape()); - } - - inline static bool touches(const _Item& sh1, const _Item& sh2) - { - return sl::touches(sh1.transformedShape(), - sh2.transformedShape()); - } - -private: - - inline const RawShape& infaltedShape() const { - if(has_inflation_ ) { - if(inflate_cache_valid_) return inflate_cache_; - - inflate_cache_ = sh_; - sl::offset(inflate_cache_, inflation_); - inflate_cache_valid_ = true; - return inflate_cache_; - } - return sh_; - } - - inline void invalidateCache() const BP2D_NOEXCEPT - { - tr_cache_valid_ = false; - lmb_valid_ = false; rmt_valid_ = false; - area_cache_valid_ = false; - inflate_cache_valid_ = false; - bb_cache_.valid = false; - convexity_ = Convexity::UNCHECKED; - } - - static inline bool vsort(const Vertex& v1, const Vertex& v2) - { - TCompute x1 = getX(v1), x2 = getX(v2); - TCompute y1 = getY(v1), y2 = getY(v2); - return y1 == y2 ? x1 < x2 : y1 < y2; - } + NestConfig() = default; + NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {} + NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {} + NestConfig(const typename Placer::Config & pcfg, + const typename Selector::Config &scfg) + : placer_config{pcfg}, selector_config{scfg} {} }; -/** - * \brief Subclass of _Item for regular rectangle items. - */ -template -class _Rectangle: public _Item { - using _Item::vertex; - using TO = Orientation; -public: - - using Unit = TCoord>; - - template::Value> - inline _Rectangle(Unit width, Unit height, - // disable this ctor if o != CLOCKWISE - enable_if_t< o == TO::CLOCKWISE, int> = 0 ): - _Item( sl::create( { - {0, 0}, - {0, height}, - {width, height}, - {width, 0}, - {0, 0} - } )) - { - } - - template::Value> - inline _Rectangle(Unit width, Unit height, - // disable this ctor if o != COUNTER_CLOCKWISE - enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ): - _Item( sl::create( { - {0, 0}, - {width, 0}, - {width, height}, - {0, height}, - {0, 0} - } )) - { - } - - inline Unit width() const BP2D_NOEXCEPT { - return getX(vertex(2)); - } - - inline Unit height() const BP2D_NOEXCEPT { - return getY(vertex(2)); - } +struct NestControl { + ProgressFunction progressfn; + StopCondition stopcond = []{ return false; }; + + NestControl() = default; + NestControl(ProgressFunction pr) : progressfn{std::move(pr)} {} + NestControl(StopCondition sc) : stopcond{std::move(sc)} {} + NestControl(ProgressFunction pr, StopCondition sc) + : progressfn{std::move(pr)}, stopcond{std::move(sc)} + {} }; -template -inline bool _Item::isInside(const _Box>& box) const { - return sl::isInside(boundingBox(), box); +template::iterator> +std::size_t nest(Iterator from, Iterator to, + const typename Placer::BinType & bin, + Coord dist = 0, + const NestConfig &cfg = {}, + NestControl ctl = {}) +{ + _Nester nester{bin, dist, cfg.placer_config, cfg.selector_config}; + if(ctl.progressfn) nester.progressIndicator(ctl.progressfn); + if(ctl.stopcond) nester.stopCondition(ctl.stopcond); + return nester.execute(from, to); } -template inline bool -_Item::isInside(const _Circle>& circ) const { - return sl::isInside(transformedShape(), circ); +#ifdef LIBNEST2D_STATIC + +extern template class _Nester; +extern template class _Nester; +extern template std::size_t nest(std::vector::iterator from, + std::vector::iterator to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); +extern template std::size_t nest(std::vector::iterator from, + std::vector::iterator to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); + +#endif + +template> +std::size_t nest(Container&& cont, + const typename Placer::BinType & bin, + Coord dist = 0, + const NestConfig &cfg = {}, + NestControl ctl = {}) +{ + return nest(cont.begin(), cont.end(), bin, dist, cfg, ctl); } -template using _ItemRef = std::reference_wrapper<_Item>; -template using _ItemGroup = std::vector<_ItemRef>; - -/** - * \brief A list of packed item vectors. Each vector represents a bin. - */ -template -using _PackGroup = std::vector>>; - -template -struct ConstItemRange { - Iterator from; - Iterator to; - bool valid = false; - - ConstItemRange() = default; - ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {} -}; - -template -inline ConstItemRange -rem(typename Container::const_iterator it, const Container& cont) { - return {std::next(it), cont.end()}; -} - -/** - * \brief A wrapper interface (trait) class for any placement strategy provider. - * - * If a client wants to use its own placement algorithm, all it has to do is to - * specialize this class template and define all the ten methods it has. It can - * use the strategies::PlacerBoilerplace class for creating a new placement - * strategy where only the constructor and the trypack method has to be provided - * and it will work out of the box. - */ -template -class PlacementStrategyLike { - PlacementStrategy impl_; -public: - - using RawShape = typename PlacementStrategy::ShapeType; - - /// The item type that the placer works with. - using Item = _Item; - - /// The placer's config type. Should be a simple struct but can be anything. - using Config = typename PlacementStrategy::Config; - - /** - * \brief The type of the bin that the placer works with. - * - * Can be a box or an arbitrary shape or just a width or height without a - * second dimension if an infinite bin is considered. - */ - using BinType = typename PlacementStrategy::BinType; - - /** - * \brief Pack result that can be used to accept or discard it. See trypack - * method. - */ - using PackResult = typename PlacementStrategy::PackResult; - - using ItemGroup = _ItemGroup; - using DefaultIterator = typename ItemGroup::const_iterator; - - /** - * @brief Constructor taking the bin and an optional configuration. - * @param bin The bin object whose type is defined by the placement strategy. - * @param config The configuration for the particular placer. - */ - explicit PlacementStrategyLike(const BinType& bin, - const Config& config = Config()): - impl_(bin) - { - configure(config); - } - - /** - * @brief Provide a different configuration for the placer. - * - * Note that it depends on the particular placer implementation how it - * reacts to config changes in the middle of a calculation. - * - * @param config The configuration object defined by the placement strategy. - */ - inline void configure(const Config& config) { impl_.configure(config); } - - /** - * Try to pack an item with a result object that contains the packing - * information for later accepting it. - * - * \param item_store A container of items that are intended to be packed - * later. Can be used by the placer to switch tactics. When it's knows that - * many items will come a greedy strategy may not be the best. - * \param from The iterator to the item from which the packing should start, - * including the pointed item - * \param count How many items should be packed. If the value is 1, than - * just the item pointed to by "from" argument should be packed. - */ - template - inline PackResult trypack( - Item& item, - const ConstItemRange& remaining = ConstItemRange()) - { - return impl_.trypack(item, remaining); - } - - /** - * @brief A method to accept a previously tried item (or items). - * - * If the pack result is a failure the method should ignore it. - * @param r The result of a previous trypack call. - */ - inline void accept(PackResult& r) { impl_.accept(r); } - - /** - * @brief pack Try to pack and immediately accept it on success. - * - * A default implementation would be to call - * { auto&& r = trypack(...); accept(r); return r; } but we should let the - * implementor of the placement strategy to harvest any optimizations from - * the absence of an intermediate step. The above version can still be used - * in the implementation. - * - * @param item The item to pack. - * @return Returns true if the item was packed or false if it could not be - * packed. - */ - template> - inline bool pack( - Item& item, - const Range& remaining = Range()) - { - return impl_.pack(item, remaining); - } - - /** - * This method makes possible to "preload" some items into the placer. It - * will not move these items but will consider them as already packed. - */ - inline void preload(const ItemGroup& packeditems) - { - impl_.preload(packeditems); - } - - /// Unpack the last element (remove it from the list of packed items). - inline void unpackLast() { impl_.unpackLast(); } - - /// Get the bin object. - inline const BinType& bin() const { return impl_.bin(); } - - /// Set a new bin object. - inline void bin(const BinType& bin) { impl_.bin(bin); } - - /// Get the packed items. - inline ItemGroup getItems() { return impl_.getItems(); } - - /// Clear the packed items so a new session can be started. - inline void clearItems() { impl_.clearItems(); } - - inline double filledArea() const { return impl_.filledArea(); } - -}; - -// The progress function will be called with the number of placed items -using ProgressFunction = std::function; -using StopCondition = std::function; - -/** - * A wrapper interface (trait) class for any selections strategy provider. - */ -template -class SelectionStrategyLike { - SelectionStrategy impl_; -public: - using RawShape = typename SelectionStrategy::ShapeType; - using Item = _Item; - using PackGroup = _PackGroup; - using Config = typename SelectionStrategy::Config; - - - /** - * @brief Provide a different configuration for the selection strategy. - * - * Note that it depends on the particular placer implementation how it - * reacts to config changes in the middle of a calculation. - * - * @param config The configuration object defined by the selection strategy. - */ - inline void configure(const Config& config) { - impl_.configure(config); - } - - /** - * @brief A function callback which should be called whenever an item or - * a group of items where successfully packed. - * @param fn A function callback object taking one unsigned integer as the - * number of the remaining items to pack. - */ - void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); } - - void stopCondition(StopCondition cond) { impl_.stopCondition(cond); } - - /** - * \brief A method to start the calculation on the input sequence. - * - * \tparam TPlacer The only mandatory template parameter is the type of - * placer compatible with the PlacementStrategyLike interface. - * - * \param first, last The first and last iterator if the input sequence. It - * can be only an iterator of a type convertible to Item. - * \param bin. The shape of the bin. It has to be supported by the placement - * strategy. - * \param An optional config object for the placer. - */ - template::BinType, - class PConfig = typename PlacementStrategyLike::Config> - inline void packItems( - TIterator first, - TIterator last, - TBin&& bin, - PConfig&& config = PConfig() ) - { - impl_.template packItems(first, last, - std::forward(bin), - std::forward(config)); - } - - /** - * @brief Get the items for a particular bin. - * @param binIndex The index of the requested bin. - * @return Returns a list of all items packed into the requested bin. - */ - inline const PackGroup& getResult() const { - return impl_.getResult(); - } - - void clear() { impl_.clear(); } -}; - -/** - * The _Nester is the front-end class for the libnest2d library. It takes the - * input items and changes their transformations to be inside the provided bin. - */ -template -class _Nester { - using TSel = SelectionStrategyLike; - TSel selector_; -public: - using Item = typename PlacementStrategy::Item; - using ShapeType = typename Item::ShapeType; - using ItemRef = std::reference_wrapper; - using TPlacer = PlacementStrategyLike; - using BinType = typename TPlacer::BinType; - using PlacementConfig = typename TPlacer::Config; - using SelectionConfig = typename TSel::Config; - using Coord = TCoord>; - using PackGroup = _PackGroup; - using ResultType = PackGroup; - -private: - BinType bin_; - PlacementConfig pconfig_; - Coord min_obj_distance_; - - using SItem = typename SelectionStrategy::Item; - using TPItem = remove_cvref_t; - using TSItem = remove_cvref_t; - - StopCondition stopfn_; - - template using TVal = remove_ref_t; - - template - using ItemIteratorOnly = - enable_if_t&, TPItem&>::value, Out>; - -public: - - /** - * \brief Constructor taking the bin as the only mandatory parameter. - * - * \param bin The bin shape that will be used by the placers. The type - * of the bin should be one that is supported by the placer type. - */ - template - _Nester(TBinType&& bin, Coord min_obj_distance = 0, - const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): - bin_(std::forward(bin)), - pconfig_(pconfig), - min_obj_distance_(min_obj_distance) - { - static_assert( std::is_same::value, - "Incompatible placement and selection strategy!"); - - selector_.configure(sconfig); - } - - void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } - void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } - void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) - { - pconfig_ = pconf; - selector_.configure(sconf); - } - void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) - { - pconfig_ = pconf; - selector_.configure(sconf); - } - - /** - * \brief Arrange an input sequence of _Item-s. - * - * To get the result, call the translation(), rotation() and binId() - * methods of each item. If only the transformed polygon is needed, call - * transformedShape() to get the properly transformed shapes. - * - * The number of groups in the pack group is the number of bins opened by - * the selection algorithm. - */ - template - inline ItemIteratorOnly execute(It from, It to) - { - auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); - if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { - item.inflate(infl); - }); - - selector_.template packItems( - from, to, bin_, pconfig_); - - if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { - item.inflate(-infl); - }); - } - - /// Set a progress indicator function object for the selector. - inline _Nester& progressIndicator(ProgressFunction func) - { - selector_.progressIndicator(func); return *this; - } - - /// Set a predicate to tell when to abort nesting. - inline _Nester& stopCondition(StopCondition fn) - { - stopfn_ = fn; selector_.stopCondition(fn); return *this; - } - - inline const PackGroup& lastResult() const - { - return selector_.getResult(); - } -}; - } #endif // LIBNEST2D_HPP diff --git a/src/libnest2d/include/libnest2d/nester.hpp b/src/libnest2d/include/libnest2d/nester.hpp new file mode 100644 index 0000000000..2f207d526b --- /dev/null +++ b/src/libnest2d/include/libnest2d/nester.hpp @@ -0,0 +1,869 @@ +#ifndef NESTER_HPP +#define NESTER_HPP + +#include +#include +#include +#include +#include +#include + +#include + +namespace libnest2d { + +static const constexpr int BIN_ID_UNSET = -1; + +/** + * \brief An item to be placed on a bin. + * + * It holds a copy of the original shape object but supports move construction + * from the shape objects if its an rvalue reference. This way we can construct + * the items without the cost of copying a potentially large amount of input. + * + * The results of some calculations are cached for maintaining fast run times. + * For this reason, memory demands are much higher but this should pay off. + */ +template +class _Item { + using Coord = TCoord>; + using Vertex = TPoint; + using Box = _Box; + + using VertexConstIterator = typename TContour::const_iterator; + + // The original shape that gets encapsulated. + RawShape sh_; + + // Transformation data + Vertex translation_{0, 0}; + Radians rotation_{0.0}; + Coord inflation_{0}; + + // Info about whether the transformations will have to take place + // This is needed because if floating point is used, it is hard to say + // that a zero angle is not a rotation because of testing for equality. + bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; + + // For caching the calculations as they can get pretty expensive. + mutable RawShape tr_cache_; + mutable bool tr_cache_valid_ = false; + mutable double area_cache_ = 0; + mutable bool area_cache_valid_ = false; + mutable RawShape inflate_cache_; + mutable bool inflate_cache_valid_ = false; + + enum class Convexity: char { + UNCHECKED, + C_TRUE, + C_FALSE + }; + + mutable Convexity convexity_ = Convexity::UNCHECKED; + mutable VertexConstIterator rmt_; // rightmost top vertex + mutable VertexConstIterator lmb_; // leftmost bottom vertex + mutable bool rmt_valid_ = false, lmb_valid_ = false; + mutable struct BBCache { + Box bb; bool valid; + BBCache(): valid(false) {} + } bb_cache_; + + int binid_{BIN_ID_UNSET}, priority_{0}; + bool fixed_{false}; + +public: + + /// The type of the shape which was handed over as the template argument. + using ShapeType = RawShape; + + /** + * \brief Iterator type for the outer vertices. + * + * Only const iterators can be used. The _Item type is not intended to + * modify the carried shapes from the outside. The main purpose of this type + * is to cache the calculation results from the various operators it + * supports. Giving out a non const iterator would make it impossible to + * perform correct cache invalidation. + */ + using Iterator = VertexConstIterator; + + /** + * @brief Get the orientation of the polygon. + * + * The orientation have to be specified as a specialization of the + * OrientationType struct which has a Value constant. + * + * @return The orientation type identifier for the _Item type. + */ + static BP2D_CONSTEXPR Orientation orientation() { + return OrientationType::Value; + } + + /** + * @brief Constructing an _Item form an existing raw shape. The shape will + * be copied into the _Item object. + * @param sh The original shape object. + */ + explicit inline _Item(const RawShape& sh): sh_(sh) {} + + /** + * @brief Construction of an item by moving the content of the raw shape, + * assuming that it supports move semantics. + * @param sh The original shape object. + */ + explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {} + + /** + * @brief Create an item from an initializer list. + * @param il The initializer list of vertices. + */ + inline _Item(const std::initializer_list< Vertex >& il): + sh_(sl::create(il)) {} + + inline _Item(const TContour& contour, + const THolesContainer& holes = {}): + sh_(sl::create(contour, holes)) {} + + inline _Item(TContour&& contour, + THolesContainer&& holes): + sh_(sl::create(std::move(contour), std::move(holes))) {} + + inline bool isFixed() const noexcept { return fixed_; } + inline void markAsFixedInBin(int binid) + { + fixed_ = binid >= 0; + binid_ = binid; + } + + inline void binId(int idx) { binid_ = idx; } + inline int binId() const noexcept { return binid_; } + + inline void priority(int p) { priority_ = p; } + inline int priority() const noexcept { return priority_; } + + /** + * @brief Convert the polygon to string representation. The format depends + * on the implementation of the polygon. + * @return + */ + inline std::string toString() const + { + return sl::toString(sh_); + } + + /// Iterator tho the first contour vertex in the polygon. + inline Iterator begin() const + { + return sl::cbegin(sh_); + } + + /// Alias to begin() + inline Iterator cbegin() const + { + return sl::cbegin(sh_); + } + + /// Iterator to the last contour vertex. + inline Iterator end() const + { + return sl::cend(sh_); + } + + /// Alias to end() + inline Iterator cend() const + { + return sl::cend(sh_); + } + + /** + * @brief Get a copy of an outer vertex within the carried shape. + * + * Note that the vertex considered here is taken from the original shape + * that this item is constructed from. This means that no transformation is + * applied to the shape in this call. + * + * @param idx The index of the requested vertex. + * @return A copy of the requested vertex. + */ + inline Vertex vertex(unsigned long idx) const + { + return sl::vertex(sh_, idx); + } + + /** + * @brief Modify a vertex. + * + * Note that this method will invalidate every cached calculation result + * including polygon offset and transformations. + * + * @param idx The index of the requested vertex. + * @param v The new vertex data. + */ + inline void setVertex(unsigned long idx, const Vertex& v ) + { + invalidateCache(); + sl::vertex(sh_, idx) = v; + } + + /** + * @brief Calculate the shape area. + * + * The method returns absolute value and does not reflect polygon + * orientation. The result is cached, subsequent calls will have very little + * cost. + * @return The shape area in floating point double precision. + */ + inline double area() const { + double ret ; + if(area_cache_valid_) ret = area_cache_; + else { + ret = sl::area(infaltedShape()); + area_cache_ = ret; + area_cache_valid_ = true; + } + return ret; + } + + inline bool isContourConvex() const { + bool ret = false; + + switch(convexity_) { + case Convexity::UNCHECKED: + ret = sl::isConvex(sl::contour(transformedShape())); + convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE; + break; + case Convexity::C_TRUE: ret = true; break; + case Convexity::C_FALSE:; + } + + return ret; + } + + inline bool isHoleConvex(unsigned /*holeidx*/) const { + return false; + } + + inline bool areHolesConvex() const { + return false; + } + + /// The number of the outer ring vertices. + inline size_t vertexCount() const { + return sl::contourVertexCount(sh_); + } + + inline size_t holeCount() const { + return sl::holeCount(sh_); + } + + /** + * @brief isPointInside + * @param p + * @return + */ + inline bool isInside(const Vertex& p) const + { + return sl::isInside(p, transformedShape()); + } + + inline bool isInside(const _Item& sh) const + { + return sl::isInside(transformedShape(), sh.transformedShape()); + } + + inline bool isInside(const RawShape& sh) const + { + return sl::isInside(transformedShape(), sh); + } + + inline bool isInside(const _Box>& box) const; + inline bool isInside(const _Circle>& box) const; + + inline void translate(const Vertex& d) BP2D_NOEXCEPT + { + translation(translation() + d); + } + + inline void rotate(const Radians& rads) BP2D_NOEXCEPT + { + rotation(rotation() + rads); + } + + inline void inflation(Coord distance) BP2D_NOEXCEPT + { + inflation_ = distance; + has_inflation_ = true; + invalidateCache(); + } + + inline Coord inflation() const BP2D_NOEXCEPT { + return inflation_; + } + + inline void inflate(Coord distance) BP2D_NOEXCEPT + { + inflation(inflation() + distance); + } + + inline Radians rotation() const BP2D_NOEXCEPT + { + return rotation_; + } + + inline TPoint translation() const BP2D_NOEXCEPT + { + return translation_; + } + + inline void rotation(Radians rot) BP2D_NOEXCEPT + { + if(rotation_ != rot) { + rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false; + rmt_valid_ = false; lmb_valid_ = false; + bb_cache_.valid = false; + } + } + + inline void translation(const TPoint& tr) BP2D_NOEXCEPT + { + if(translation_ != tr) { + translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; + //bb_cache_.valid = false; + } + } + + inline const RawShape& transformedShape() const + { + if(tr_cache_valid_) return tr_cache_; + + RawShape cpy = infaltedShape(); + if(has_rotation_) sl::rotate(cpy, rotation_); + if(has_translation_) sl::translate(cpy, translation_); + tr_cache_ = cpy; tr_cache_valid_ = true; + rmt_valid_ = false; lmb_valid_ = false; + + return tr_cache_; + } + + inline operator RawShape() const + { + return transformedShape(); + } + + inline const RawShape& rawShape() const BP2D_NOEXCEPT + { + return sh_; + } + + inline void resetTransformation() BP2D_NOEXCEPT + { + has_translation_ = false; has_rotation_ = false; has_inflation_ = false; + invalidateCache(); + } + + inline Box boundingBox() const { + if(!bb_cache_.valid) { + if(!has_rotation_) + bb_cache_.bb = sl::boundingBox(infaltedShape()); + else { + // TODO make sure this works + auto rotsh = infaltedShape(); + sl::rotate(rotsh, rotation_); + bb_cache_.bb = sl::boundingBox(rotsh); + } + bb_cache_.valid = true; + } + + auto &bb = bb_cache_.bb; auto &tr = translation_; + return {bb.minCorner() + tr, bb.maxCorner() + tr }; + } + + inline Vertex referenceVertex() const { + return rightmostTopVertex(); + } + + inline Vertex rightmostTopVertex() const { + if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex + auto& tsh = transformedShape(); + rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort); + rmt_valid_ = true; + } + return *rmt_; + } + + inline Vertex leftmostBottomVertex() const { + if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex + auto& tsh = transformedShape(); + lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort); + lmb_valid_ = true; + } + return *lmb_; + } + + //Static methods: + + inline static bool intersects(const _Item& sh1, const _Item& sh2) + { + return sl::intersects(sh1.transformedShape(), + sh2.transformedShape()); + } + + inline static bool touches(const _Item& sh1, const _Item& sh2) + { + return sl::touches(sh1.transformedShape(), + sh2.transformedShape()); + } + +private: + + inline const RawShape& infaltedShape() const { + if(has_inflation_ ) { + if(inflate_cache_valid_) return inflate_cache_; + + inflate_cache_ = sh_; + sl::offset(inflate_cache_, inflation_); + inflate_cache_valid_ = true; + return inflate_cache_; + } + return sh_; + } + + inline void invalidateCache() const BP2D_NOEXCEPT + { + tr_cache_valid_ = false; + lmb_valid_ = false; rmt_valid_ = false; + area_cache_valid_ = false; + inflate_cache_valid_ = false; + bb_cache_.valid = false; + convexity_ = Convexity::UNCHECKED; + } + + static inline bool vsort(const Vertex& v1, const Vertex& v2) + { + TCompute x1 = getX(v1), x2 = getX(v2); + TCompute y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; + } +}; + +/** + * \brief Subclass of _Item for regular rectangle items. + */ +template +class _Rectangle: public _Item { + using _Item::vertex; + using TO = Orientation; +public: + + using Unit = TCoord>; + + template::Value> + inline _Rectangle(Unit width, Unit height, + // disable this ctor if o != CLOCKWISE + enable_if_t< o == TO::CLOCKWISE, int> = 0 ): + _Item( sl::create( { + {0, 0}, + {0, height}, + {width, height}, + {width, 0}, + {0, 0} + } )) + { + } + + template::Value> + inline _Rectangle(Unit width, Unit height, + // disable this ctor if o != COUNTER_CLOCKWISE + enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ): + _Item( sl::create( { + {0, 0}, + {width, 0}, + {width, height}, + {0, height}, + {0, 0} + } )) + { + } + + inline Unit width() const BP2D_NOEXCEPT { + return getX(vertex(2)); + } + + inline Unit height() const BP2D_NOEXCEPT { + return getY(vertex(2)); + } +}; + +template +inline bool _Item::isInside(const _Box>& box) const { + return sl::isInside(boundingBox(), box); +} + +template inline bool +_Item::isInside(const _Circle>& circ) const { + return sl::isInside(transformedShape(), circ); +} + +template using _ItemRef = std::reference_wrapper<_Item>; +template using _ItemGroup = std::vector<_ItemRef>; + +/** + * \brief A list of packed item vectors. Each vector represents a bin. + */ +template +using _PackGroup = std::vector>>; + +template +struct ConstItemRange { + Iterator from; + Iterator to; + bool valid = false; + + ConstItemRange() = default; + ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {} +}; + +template +inline ConstItemRange +rem(typename Container::const_iterator it, const Container& cont) { + return {std::next(it), cont.end()}; +} + +/** + * \brief A wrapper interface (trait) class for any placement strategy provider. + * + * If a client wants to use its own placement algorithm, all it has to do is to + * specialize this class template and define all the ten methods it has. It can + * use the strategies::PlacerBoilerplace class for creating a new placement + * strategy where only the constructor and the trypack method has to be provided + * and it will work out of the box. + */ +template +class PlacementStrategyLike { + PlacementStrategy impl_; +public: + + using RawShape = typename PlacementStrategy::ShapeType; + + /// The item type that the placer works with. + using Item = _Item; + + /// The placer's config type. Should be a simple struct but can be anything. + using Config = typename PlacementStrategy::Config; + + /** + * \brief The type of the bin that the placer works with. + * + * Can be a box or an arbitrary shape or just a width or height without a + * second dimension if an infinite bin is considered. + */ + using BinType = typename PlacementStrategy::BinType; + + /** + * \brief Pack result that can be used to accept or discard it. See trypack + * method. + */ + using PackResult = typename PlacementStrategy::PackResult; + + using ItemGroup = _ItemGroup; + using DefaultIterator = typename ItemGroup::const_iterator; + + /** + * @brief Constructor taking the bin and an optional configuration. + * @param bin The bin object whose type is defined by the placement strategy. + * @param config The configuration for the particular placer. + */ + explicit PlacementStrategyLike(const BinType& bin, + const Config& config = Config()): + impl_(bin) + { + configure(config); + } + + /** + * @brief Provide a different configuration for the placer. + * + * Note that it depends on the particular placer implementation how it + * reacts to config changes in the middle of a calculation. + * + * @param config The configuration object defined by the placement strategy. + */ + inline void configure(const Config& config) { impl_.configure(config); } + + /** + * Try to pack an item with a result object that contains the packing + * information for later accepting it. + * + * \param item_store A container of items that are intended to be packed + * later. Can be used by the placer to switch tactics. When it's knows that + * many items will come a greedy strategy may not be the best. + * \param from The iterator to the item from which the packing should start, + * including the pointed item + * \param count How many items should be packed. If the value is 1, than + * just the item pointed to by "from" argument should be packed. + */ + template + inline PackResult trypack( + Item& item, + const ConstItemRange& remaining = ConstItemRange()) + { + return impl_.trypack(item, remaining); + } + + /** + * @brief A method to accept a previously tried item (or items). + * + * If the pack result is a failure the method should ignore it. + * @param r The result of a previous trypack call. + */ + inline void accept(PackResult& r) { impl_.accept(r); } + + /** + * @brief pack Try to pack and immediately accept it on success. + * + * A default implementation would be to call + * { auto&& r = trypack(...); accept(r); return r; } but we should let the + * implementor of the placement strategy to harvest any optimizations from + * the absence of an intermediate step. The above version can still be used + * in the implementation. + * + * @param item The item to pack. + * @return Returns true if the item was packed or false if it could not be + * packed. + */ + template> + inline bool pack( + Item& item, + const Range& remaining = Range()) + { + return impl_.pack(item, remaining); + } + + /** + * This method makes possible to "preload" some items into the placer. It + * will not move these items but will consider them as already packed. + */ + inline void preload(const ItemGroup& packeditems) + { + impl_.preload(packeditems); + } + + /// Unpack the last element (remove it from the list of packed items). + inline void unpackLast() { impl_.unpackLast(); } + + /// Get the bin object. + inline const BinType& bin() const { return impl_.bin(); } + + /// Set a new bin object. + inline void bin(const BinType& bin) { impl_.bin(bin); } + + /// Get the packed items. + inline ItemGroup getItems() { return impl_.getItems(); } + + /// Clear the packed items so a new session can be started. + inline void clearItems() { impl_.clearItems(); } + + inline double filledArea() const { return impl_.filledArea(); } + +}; + +// The progress function will be called with the number of placed items +using ProgressFunction = std::function; +using StopCondition = std::function; + +/** + * A wrapper interface (trait) class for any selections strategy provider. + */ +template +class SelectionStrategyLike { + SelectionStrategy impl_; +public: + using RawShape = typename SelectionStrategy::ShapeType; + using Item = _Item; + using PackGroup = _PackGroup; + using Config = typename SelectionStrategy::Config; + + + /** + * @brief Provide a different configuration for the selection strategy. + * + * Note that it depends on the particular placer implementation how it + * reacts to config changes in the middle of a calculation. + * + * @param config The configuration object defined by the selection strategy. + */ + inline void configure(const Config& config) { + impl_.configure(config); + } + + /** + * @brief A function callback which should be called whenever an item or + * a group of items where successfully packed. + * @param fn A function callback object taking one unsigned integer as the + * number of the remaining items to pack. + */ + void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); } + + void stopCondition(StopCondition cond) { impl_.stopCondition(cond); } + + /** + * \brief A method to start the calculation on the input sequence. + * + * \tparam TPlacer The only mandatory template parameter is the type of + * placer compatible with the PlacementStrategyLike interface. + * + * \param first, last The first and last iterator if the input sequence. It + * can be only an iterator of a type convertible to Item. + * \param bin. The shape of the bin. It has to be supported by the placement + * strategy. + * \param An optional config object for the placer. + */ + template::BinType, + class PConfig = typename PlacementStrategyLike::Config> + inline void packItems( + TIterator first, + TIterator last, + TBin&& bin, + PConfig&& config = PConfig() ) + { + impl_.template packItems(first, last, + std::forward(bin), + std::forward(config)); + } + + /** + * @brief Get the items for a particular bin. + * @param binIndex The index of the requested bin. + * @return Returns a list of all items packed into the requested bin. + */ + inline const PackGroup& getResult() const { + return impl_.getResult(); + } + + void clear() { impl_.clear(); } +}; + +/** + * The _Nester is the front-end class for the libnest2d library. It takes the + * input items and changes their transformations to be inside the provided bin. + */ +template +class _Nester { + using TSel = SelectionStrategyLike; + TSel selector_; + +public: + using Item = typename PlacementStrategy::Item; + using ShapeType = typename Item::ShapeType; + using ItemRef = std::reference_wrapper; + using TPlacer = PlacementStrategyLike; + using BinType = typename TPlacer::BinType; + using PlacementConfig = typename TPlacer::Config; + using SelectionConfig = typename TSel::Config; + using Coord = TCoord>; + using PackGroup = _PackGroup; + using ResultType = PackGroup; + +private: + BinType bin_; + PlacementConfig pconfig_; + Coord min_obj_distance_; + + using SItem = typename SelectionStrategy::Item; + using TPItem = remove_cvref_t; + using TSItem = remove_cvref_t; + + StopCondition stopfn_; + + template using TVal = remove_ref_t; + + template + using ItemIteratorOnly = + enable_if_t&, TPItem&>::value, Out>; + +public: + + /** + * \brief Constructor taking the bin as the only mandatory parameter. + * + * \param bin The bin shape that will be used by the placers. The type + * of the bin should be one that is supported by the placer type. + */ + template + _Nester(TBinType&& bin, Coord min_obj_distance = 0, + const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): + bin_(std::forward(bin)), + pconfig_(pconfig), + min_obj_distance_(min_obj_distance) + { + static_assert( std::is_same::value, + "Incompatible placement and selection strategy!"); + + selector_.configure(sconfig); + } + + void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } + void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } + void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) + { + pconfig_ = pconf; + selector_.configure(sconf); + } + void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) + { + pconfig_ = pconf; + selector_.configure(sconf); + } + + /** + * \brief Arrange an input sequence of _Item-s. + * + * To get the result, call the translation(), rotation() and binId() + * methods of each item. If only the transformed polygon is needed, call + * transformedShape() to get the properly transformed shapes. + * + * The number of groups in the pack group is the number of bins opened by + * the selection algorithm. + */ + template + inline ItemIteratorOnly execute(It from, It to) + { + auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); + if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { + item.inflate(infl); + }); + + selector_.template packItems( + from, to, bin_, pconfig_); + + if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { + item.inflate(-infl); + }); + + return selector_.getResult().size(); + } + + /// Set a progress indicator function object for the selector. + inline _Nester& progressIndicator(ProgressFunction func) + { + selector_.progressIndicator(func); return *this; + } + + /// Set a predicate to tell when to abort nesting. + inline _Nester& stopCondition(StopCondition fn) + { + stopfn_ = fn; selector_.stopCondition(fn); return *this; + } + + inline const PackGroup& lastResult() const + { + return selector_.getResult(); + } +}; + +} + +#endif // NESTER_HPP diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt deleted file mode 100644 index 6f51718d8b..0000000000 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt +++ /dev/null @@ -1,61 +0,0 @@ -find_package(NLopt 1.4) - -if(NOT NLopt_FOUND) - message(STATUS "NLopt not found so downloading " - "and automatic build is performed...") - - include(DownloadProject) - - if (CMAKE_VERSION VERSION_LESS 3.2) - set(UPDATE_DISCONNECTED_IF_AVAILABLE "") - else() - set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") - endif() - - set(URL_NLOPT "https://github.com/stevengj/nlopt.git" - CACHE STRING "Location of the nlopt git repository") - - # set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt) - include(DownloadProject) - download_project( PROJ nlopt - GIT_REPOSITORY ${URL_NLOPT} - GIT_TAG v2.5.0 - # CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR} - ${UPDATE_DISCONNECTED_IF_AVAILABLE} - ) - - set(SHARED_LIBS_STATE BUILD_SHARED_LIBS) - set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) - set(NLOPT_PYTHON OFF CACHE BOOL "" FORCE) - set(NLOPT_OCTAVE OFF CACHE BOOL "" FORCE) - set(NLOPT_MATLAB OFF CACHE BOOL "" FORCE) - set(NLOPT_GUILE OFF CACHE BOOL "" FORCE) - set(NLOPT_SWIG OFF CACHE BOOL "" FORCE) - set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE) - - add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR}) - - set(NLopt_LIBS nlopt) - set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR} ${nlopt_BINARY_DIR}/src/api) - set(SHARED_LIBS_STATE ${SHARED_STATE}) - - add_library(nloptOptimizer INTERFACE) - target_link_libraries(nloptOptimizer INTERFACE nlopt) - target_include_directories(nloptOptimizer INTERFACE ${NLopt_INCLUDE_DIR}) - -else() - add_library(nloptOptimizer INTERFACE) - target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt) -endif() - -#target_sources( nloptOptimizer INTERFACE -#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp -#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp -#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp -#${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp -#) - -target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) - -# And finally plug the nloptOptimizer into libnest2d -#target_link_libraries(libnest2d INTERFACE nloptOptimizer) diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 686857a87f..0030287586 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -1122,8 +1122,6 @@ private: sl::rotate(sh, item.rotation()); Box bb = sl::boundingBox(sh); - bb.minCorner() += item.translation(); - bb.maxCorner() += item.translation(); Vertex ci, cb; auto bbin = sl::boundingBox(bin_); diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index 26681aeec6..dc1bbd4f1f 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -1,7 +1,7 @@ #ifndef PLACER_BOILERPLATE_HPP #define PLACER_BOILERPLATE_HPP -#include +#include namespace libnest2d { namespace placers { diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index 36fec71647..8e65bafe98 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -2,7 +2,7 @@ #define SELECTION_BOILERPLATE_HPP #include -#include +#include namespace libnest2d { namespace selections { @@ -25,7 +25,7 @@ public: inline void clear() { packed_bins_.clear(); } protected: - + template void remove_unpackable_items(Container &c, const Bin &bin, const PCfg& pcfg) { @@ -33,14 +33,14 @@ protected: // then it should be removed from the list auto it = c.begin(); while (it != c.end() && !stopcond_()) { - + // WARNING: The copy of itm needs to be created before Placer. // Placer is working with references and its destructor still // manipulates the item this is why the order of stack creation - // matters here. + // matters here. const Item& itm = *it; Item cpy{itm}; - + Placer p{bin}; p.configure(pcfg); if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it); diff --git a/src/libnest2d/src/libnest2d.cpp b/src/libnest2d/src/libnest2d.cpp index 0214587874..5a881541e2 100644 --- a/src/libnest2d/src/libnest2d.cpp +++ b/src/libnest2d/src/libnest2d.cpp @@ -1,23 +1,26 @@ -#include +#include namespace libnest2d { -template class Nester; -template class Nester; +template class _Nester; +template class _Nester; -template PackGroup nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); +template std::size_t _Nester::execute( + std::vector::iterator, std::vector::iterator); +template std::size_t _Nester::execute( + std::vector::iterator, std::vector::iterator); -template PackGroup nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); +template std::size_t nest(std::vector::iterator from, + std::vector::iterator to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); + +template std::size_t nest(std::vector::iterator from, + std::vector::iterator to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); } diff --git a/src/libnest2d/tests/CMakeLists.txt b/src/libnest2d/tests/CMakeLists.txt deleted file mode 100644 index 1b7d8e3ae6..0000000000 --- a/src/libnest2d/tests/CMakeLists.txt +++ /dev/null @@ -1,60 +0,0 @@ - -# Try to find existing GTest installation -find_package(GTest 1.7) - -if(NOT GTEST_FOUND) - set(URL_GTEST "https://github.com/google/googletest.git" - CACHE STRING "Google test source code repository location.") - - message(STATUS "GTest not found so downloading from ${URL_GTEST}") - # Go and download google test framework, integrate it with the build - set(GTEST_LIBS_TO_LINK gtest gtest_main) - - if (CMAKE_VERSION VERSION_LESS 3.2) - set(UPDATE_DISCONNECTED_IF_AVAILABLE "") - else() - set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") - endif() - - include(DownloadProject) - download_project(PROJ googletest - GIT_REPOSITORY ${URL_GTEST} - GIT_TAG release-1.7.0 - ${UPDATE_DISCONNECTED_IF_AVAILABLE} - ) - - # Prevent GoogleTest from overriding our compiler/linker options - # when building with Visual Studio - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - add_subdirectory(${googletest_SOURCE_DIR} - ${googletest_BINARY_DIR} - ) - - set(GTEST_INCLUDE_DIRS ${googletest_SOURCE_DIR}/include) - -else() - find_package(Threads REQUIRED) - set(GTEST_LIBS_TO_LINK ${GTEST_BOTH_LIBRARIES} Threads::Threads) -endif() - -add_executable(tests_clipper_nlopt - test.cpp - ../tools/svgtools.hpp -# ../tools/libnfpglue.hpp -# ../tools/libnfpglue.cpp - printer_parts.h - printer_parts.cpp -) - -target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) - -target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS}) - -if(NOT LIBNEST2D_HEADER_ONLY) - target_link_libraries(tests_clipper_nlopt ${LIBNAME}) -else() - target_link_libraries(tests_clipper_nlopt libnest2d) -endif() - -add_test(libnest2d_tests tests_clipper_nlopt) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 20dfd8926f..3fa7e1841a 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -375,7 +375,7 @@ public: for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { Item& itm = fixeditems[idx]; - itm.markAsFixed(); + itm.markAsFixedInBin(itm.binId()); } m_pck.configure(m_pconf); diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index b1ebdcfbc7..4fbe72163c 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -15,7 +15,7 @@ public: PointClass max; bool defined; - BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {} + BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) @@ -59,7 +59,7 @@ template class BoundingBox3Base : public BoundingBoxBase { public: - BoundingBox3Base() : BoundingBoxBase() {}; + BoundingBox3Base() : BoundingBoxBase() {} BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } @@ -100,6 +100,33 @@ public: } }; +// Will prevent warnings caused by non existing definition of template in hpp +extern template void BoundingBoxBase::scale(double factor); +extern template void BoundingBoxBase::scale(double factor); +extern template void BoundingBoxBase::scale(double factor); +extern template void BoundingBoxBase::offset(coordf_t delta); +extern template void BoundingBoxBase::offset(coordf_t delta); +extern template void BoundingBoxBase::merge(const Point &point); +extern template void BoundingBoxBase::merge(const Vec2d &point); +extern template void BoundingBoxBase::merge(const Points &points); +extern template void BoundingBoxBase::merge(const Pointfs &points); +extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +extern template Point BoundingBoxBase::size() const; +extern template Vec2d BoundingBoxBase::size() const; +extern template double BoundingBoxBase::radius() const; +extern template double BoundingBoxBase::radius() const; +extern template Point BoundingBoxBase::center() const; +extern template Vec2d BoundingBoxBase::center() const; +extern template void BoundingBox3Base::merge(const Vec3d &point); +extern template void BoundingBox3Base::merge(const Pointf3s &points); +extern template void BoundingBox3Base::merge(const BoundingBox3Base &bb); +extern template Vec3d BoundingBox3Base::size() const; +extern template double BoundingBox3Base::radius() const; +extern template void BoundingBox3Base::offset(coordf_t delta); +extern template Vec3d BoundingBox3Base::center() const; +extern template coordf_t BoundingBox3Base::max_size() const; + class BoundingBox : public BoundingBoxBase { public: @@ -113,9 +140,9 @@ public: // to encompass the original bounding box. void align_to_grid(const coord_t cell_size); - BoundingBox() : BoundingBoxBase() {}; - BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {}; - BoundingBox(const Points &points) : BoundingBoxBase(points) {}; + BoundingBox() : BoundingBoxBase() {} + BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} + BoundingBox(const Points &points) : BoundingBoxBase(points) {} BoundingBox(const Lines &lines); friend BoundingBox get_extents_rotated(const Points &points, double angle); @@ -124,25 +151,25 @@ public: class BoundingBox3 : public BoundingBox3Base { public: - BoundingBox3() : BoundingBox3Base() {}; - BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base(pmin, pmax) {}; - BoundingBox3(const Points3& points) : BoundingBox3Base(points) {}; + BoundingBox3() : BoundingBox3Base() {} + BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base(pmin, pmax) {} + BoundingBox3(const Points3& points) : BoundingBox3Base(points) {} }; class BoundingBoxf : public BoundingBoxBase { public: - BoundingBoxf() : BoundingBoxBase() {}; - BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase(pmin, pmax) {}; - BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}; + BoundingBoxf() : BoundingBoxBase() {} + BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase(pmin, pmax) {} + BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {} }; class BoundingBoxf3 : public BoundingBox3Base { public: - BoundingBoxf3() : BoundingBox3Base() {}; - BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {}; - BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}; + BoundingBoxf3() : BoundingBox3Base() {} + BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {} + BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {} BoundingBoxf3 transformed(const Transform3d& matrix) const; }; diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index ccc3505cea..ce7c960fa3 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -6,9 +6,9 @@ namespace Slic3r { BridgeDetector::BridgeDetector( - ExPolygon _expolygon, - const ExPolygonCollection &_lower_slices, - coord_t _spacing) : + ExPolygon _expolygon, + const ExPolygons &_lower_slices, + coord_t _spacing) : // The original infill polygon, not inflated. expolygons(expolygons_owned), // All surfaces of the object supporting this region. @@ -20,9 +20,9 @@ BridgeDetector::BridgeDetector( } BridgeDetector::BridgeDetector( - const ExPolygons &_expolygons, - const ExPolygonCollection &_lower_slices, - coord_t _spacing) : + const ExPolygons &_expolygons, + const ExPolygons &_lower_slices, + coord_t _spacing) : // The original infill polygon, not inflated. expolygons(_expolygons), // All surfaces of the object supporting this region. @@ -46,7 +46,11 @@ void BridgeDetector::initialize() // Detect what edges lie on lower slices by turning bridge contour and holes // into polylines and then clipping them with each lower slice's contour. // Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()). - this->_edges = intersection_pl(to_polylines(grown), this->lower_slices.contours()); + Polygons contours; + contours.reserve(this->lower_slices.size()); + for (const ExPolygon &expoly : this->lower_slices) + contours.push_back(expoly.contour); + this->_edges = intersection_pl(to_polylines(grown), contours); #ifdef SLIC3R_DEBUG printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); @@ -54,7 +58,7 @@ void BridgeDetector::initialize() // detect anchors as intersection between our bridge expolygon and the lower slices // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges - this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices.expolygons), true); + this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices), true); /* if (0) { @@ -271,7 +275,7 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const if (angle == -1) angle = this->angle; if (angle == -1) return; - Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing)); + Polygons grown_lower = offset(this->lower_slices, float(this->spacing)); for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) { // get unsupported bridge edges (both contour and holes) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index 5c55276bed..f876f83c76 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -3,7 +3,6 @@ #include "libslic3r.h" #include "ExPolygon.hpp" -#include "ExPolygonCollection.hpp" #include namespace Slic3r { @@ -21,7 +20,7 @@ public: // In case the caller gaves us the input polygons by a value, make a copy. ExPolygons expolygons_owned; // Lower slices, all regions. - const ExPolygonCollection &lower_slices; + const ExPolygons &lower_slices; // Scaled extrusion width of the infill. coord_t spacing; // Angle resolution for the brute force search of the best bridging angle. @@ -29,8 +28,8 @@ public: // The final optimal angle. double angle; - BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); - BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); + BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_width); + BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_width); // If bridge_direction_override != 0, then the angle is used instead of auto-detect. bool detect_angle(double bridge_direction_override = 0.); Polygons coverage(double angle = -1) const; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 97e0fc09b0..c8e259caa9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -22,6 +22,8 @@ add_library(libslic3r STATIC Config.hpp EdgeGrid.cpp EdgeGrid.hpp + ElephantFootCompensation.cpp + ElephantFootCompensation.hpp ExPolygon.cpp ExPolygon.hpp ExPolygonCollection.cpp @@ -71,6 +73,8 @@ add_library(libslic3r STATIC Format/STL.hpp GCode/Analyzer.cpp GCode/Analyzer.hpp + GCode/ThumbnailData.cpp + GCode/ThumbnailData.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/PostProcessor.cpp @@ -100,7 +104,7 @@ add_library(libslic3r STATIC Geometry.cpp Geometry.hpp Int128.hpp -# KdTree.hpp + KDTreeIndirect.hpp Layer.cpp Layer.hpp LayerRegion.cpp @@ -131,8 +135,6 @@ add_library(libslic3r STATIC PolygonTrimmer.hpp Polyline.cpp Polyline.hpp - PolylineCollection.cpp - PolylineCollection.hpp Print.cpp Print.hpp PrintBase.cpp @@ -142,6 +144,8 @@ add_library(libslic3r STATIC PrintObject.cpp PrintRegion.cpp Semver.cpp + ShortestPath.cpp + ShortestPath.hpp SLAPrint.cpp SLAPrint.hpp SLA/SLAAutoSupports.hpp @@ -176,8 +180,13 @@ add_library(libslic3r STATIC miniz_extension.cpp SLA/SLACommon.hpp SLA/SLABoilerPlate.hpp - SLA/SLABasePool.hpp - SLA/SLABasePool.cpp + SLA/SLAPad.hpp + SLA/SLAPad.cpp + SLA/SLASupportTreeBuilder.hpp + SLA/SLASupportTreeBuildsteps.hpp + SLA/SLASupportTreeBuildsteps.cpp + SLA/SLASupportTreeBuilder.cpp + SLA/SLAConcurrency.hpp SLA/SLASupportTree.hpp SLA/SLASupportTree.cpp SLA/SLASupportTreeIGL.cpp @@ -189,6 +198,8 @@ add_library(libslic3r STATIC SLA/SLARaster.cpp SLA/SLARasterWriter.hpp SLA/SLARasterWriter.cpp + SLA/ConcaveHull.hpp + SLA/ConcaveHull.cpp ) encoding_check(libslic3r) @@ -197,7 +208,7 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) endif () -target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) +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_link_libraries(libslic3r libnest2d @@ -214,7 +225,9 @@ target_link_libraries(libslic3r poly2tri qhull semver - tbb + TBB::tbb + # OpenVDB::openvdb + ${CMAKE_DL_LIBS} ) if(WIN32) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 4c6e542f4d..25100b22fe 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1,5 +1,6 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include "ShortestPath.hpp" // #define CLIPPER_UTILS_DEBUG @@ -106,8 +107,7 @@ void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* ex } } -ExPolygons -PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) +ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) { ExPolygons retval; for (int i = 0; i < polytree.ChildCount(); ++i) @@ -150,8 +150,7 @@ Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input return retval; } -ExPolygons -ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) +ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) { // init Clipper ClipperLib::Clipper clipper; @@ -166,8 +165,7 @@ ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) return PolyTreeToExPolygons(polytree); } -ClipperLib::Path -Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) +ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) { ClipperLib::Path retval; for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) @@ -175,8 +173,7 @@ Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) return retval; } -ClipperLib::Path -Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) +ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) { ClipperLib::Path output; output.reserve(input.points.size()); @@ -193,6 +190,19 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input) return retval; } +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input) +{ + ClipperLib::Paths retval; + for (auto &ep : input) { + retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(ep.contour)); + + for (auto &h : ep.holes) + retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(h)); + } + + return retval; +} + ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) { ClipperLib::Paths retval; @@ -471,14 +481,16 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, return union_ex(polys); } -template -T -_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject, - const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) +template +T _clipper_do(const ClipperLib::ClipType clipType, + TSubj && subject, + TClip && clip, + const ClipperLib::PolyFillType fillType, + const bool safety_offset_) { // read input - ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); - ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward(subject)); + ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(std::forward(clip)); // perform safety offset if (safety_offset_) { @@ -505,7 +517,7 @@ _clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject, // Fix of #117: A large fractal pyramid takes ages to slice // The Clipper library has difficulties processing overlapping polygons. -// Namely, the function Clipper::JoinCommonEdges() has potentially a terrible time complexity if the output +// Namely, the function ClipperLib::JoinCommonEdges() has potentially a terrible time complexity if the output // of the operation is of the PolyTree type. // This function implmenets a following workaround: // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. @@ -647,12 +659,26 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons return retval; } -ClipperLib::PolyTree -union_pt(const Polygons &subject, bool safety_offset_) +ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_) { return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); } +ClipperLib::PolyTree union_pt(const ExPolygons &subject, bool safety_offset_) +{ + return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); +} + +ClipperLib::PolyTree union_pt(Polygons &&subject, bool safety_offset_) +{ + return _clipper_do(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); +} + +ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_) +{ + return _clipper_do(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_); +} + Polygons union_pt_chained(const Polygons &subject, bool safety_offset_) { @@ -663,30 +689,123 @@ union_pt_chained(const Polygons &subject, bool safety_offset_) return retval; } -void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) +static ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes) +{ + // collect ordering points + Points ordering_points; + ordering_points.reserve(nodes.size()); + for (const ClipperLib::PolyNode *node : nodes) + ordering_points.emplace_back(Point(node->Contour.front().X, node->Contour.front().Y)); + + // perform the ordering + ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); + + return ordered_nodes; +} + +enum class e_ordering { + ORDER_POLYNODES, + DONT_ORDER_POLYNODES +}; + +template +void foreach_node(const ClipperLib::PolyNodes &nodes, + std::function fn); + +template<> void foreach_node( + const ClipperLib::PolyNodes & nodes, + std::function fn) +{ + for (auto &n : nodes) fn(n); +} + +template<> void foreach_node( + const ClipperLib::PolyNodes & nodes, + std::function fn) +{ + auto ordered_nodes = order_nodes(nodes); + for (auto &n : ordered_nodes) fn(n); +} + +template +void _traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) { /* use a nearest neighbor search to order these children TODO: supply start_near to chained_path() too? */ - // collect ordering points - Points ordering_points; - ordering_points.reserve(nodes.size()); - for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { - Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); - ordering_points.emplace_back(p); - } - - // perform the ordering - ClipperLib::PolyNodes ordered_nodes; - Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes); - // push results recursively - for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { + foreach_node(nodes, [&retval](const ClipperLib::PolyNode *node) { // traverse the next depth - traverse_pt((*it)->Childs, retval); - retval->emplace_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); - if ((*it)->IsHole()) retval->back().reverse(); // ccw - } + _traverse_pt(node->Childs, retval); + retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour)); + if (node->IsHole()) retval->back().reverse(); // ccw + }); +} + +template +void _traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) +{ + if (!retval || !tree) return; + + ExPolygons &retv = *retval; + + std::function hole_fn; + + auto contour_fn = [&retv, &hole_fn](const ClipperLib::PolyNode *pptr) { + ExPolygon poly; + poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); + auto fn = std::bind(hole_fn, std::placeholders::_1, poly); + foreach_node(pptr->Childs, fn); + retv.push_back(poly); + }; + + hole_fn = [&contour_fn](const ClipperLib::PolyNode *pptr, ExPolygon& poly) + { + poly.holes.emplace_back(); + poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); + foreach_node(pptr->Childs, contour_fn); + }; + + contour_fn(tree); +} + +template +void _traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) +{ + // Here is the actual traverse + foreach_node(nodes, [&retval](const ClipperLib::PolyNode *node) { + _traverse_pt(node, retval); + }); +} + +void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval) +{ + _traverse_pt(tree, retval); +} + +void traverse_pt_unordered(const ClipperLib::PolyNode *tree, ExPolygons *retval) +{ + _traverse_pt(tree, retval); +} + +void traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval) +{ + _traverse_pt(nodes, retval); +} + +void traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) +{ + _traverse_pt(nodes, retval); +} + +void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Polygons *retval) +{ + _traverse_pt(nodes, retval); +} + +void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, ExPolygons *retval) +{ + _traverse_pt(nodes, retval); } Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) @@ -795,4 +914,330 @@ Polygons top_level_islands(const Slic3r::Polygons &polygons) return out; } +// Outer offset shall not split the input contour into multiples. It is expected, that the solution will be non empty and it will contain just a single polygon. +ClipperLib::Paths fix_after_outer_offset(const ClipperLib::Path &input, ClipperLib::PolyFillType filltype, bool reverse_result) +{ + ClipperLib::Paths solution; + if (! input.empty()) { + ClipperLib::Clipper clipper; + clipper.AddPath(input, ClipperLib::ptSubject, true); + clipper.ReverseSolution(reverse_result); + clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype); + } + return solution; +} + +// Inner offset may split the source contour into multiple contours, but one shall not be inside the other. +ClipperLib::Paths fix_after_inner_offset(const ClipperLib::Path &input, ClipperLib::PolyFillType filltype, bool reverse_result) +{ + ClipperLib::Paths solution; + if (! input.empty()) { + ClipperLib::Clipper clipper; + clipper.AddPath(input, ClipperLib::ptSubject, true); + ClipperLib::IntRect r = clipper.GetBounds(); + r.left -= 10; r.top -= 10; r.right += 10; r.bottom += 10; + if (filltype == ClipperLib::pftPositive) + clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.left, r.top), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.right, r.bottom) }, ClipperLib::ptSubject, true); + else + clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.right, r.bottom), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.left, r.top) }, ClipperLib::ptSubject, true); + clipper.ReverseSolution(reverse_result); + clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype); + if (! solution.empty()) + solution.erase(solution.begin()); + } + return solution; +} + +ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector &deltas, double miter_limit) +{ + assert(contour.size() == deltas.size()); + +#ifndef NDEBUG + // Verify that the deltas are either all positive, or all negative. + bool positive = false; + bool negative = false; + for (float delta : deltas) + if (delta < 0.f) + negative = true; + else if (delta > 0.f) + positive = true; + assert(! (negative && positive)); +#endif /* NDEBUG */ + + ClipperLib::Path out; + + if (deltas.size() > 2) + { + out.reserve(contour.size() * 2); + + // Clamp miter limit to 2. + miter_limit = (miter_limit > 2.) ? 2. / (miter_limit * miter_limit) : 0.5; + + // perpenduclar vector + auto perp = [](const Vec2d &v) -> Vec2d { return Vec2d(v.y(), - v.x()); }; + + // Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt. + auto add_offset_point = [&out](Vec2d pt) { + pt *= double(CLIPPER_OFFSET_SCALE); + pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0)); + out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y())); + }; + + // Minimum edge length, squared. + double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR; + double l2min = lmin * lmin; + // Minimum angle to consider two edges to be parallel. + // Vojtech's estimate. +// const double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE); + // Implementation equal to Clipper. + const double sin_min_parallel = 1.; + + // Find the last point further from pt by l2min. + Vec2d pt = contour.front().cast(); + size_t iprev = contour.size() - 1; + Vec2d ptprev; + for (; iprev > 0; -- iprev) { + ptprev = contour[iprev].cast(); + if ((ptprev - pt).squaredNorm() > l2min) + break; + } + + if (iprev != 0) { + size_t ilast = iprev; + // Normal to the (pt - ptprev) segment. + Vec2d nprev = perp(pt - ptprev).normalized(); + for (size_t i = 0; ; ) { + // Find the next point further from pt by l2min. + size_t j = i + 1; + Vec2d ptnext; + for (; j <= ilast; ++ j) { + ptnext = contour[j].cast(); + double l2 = (ptnext - pt).squaredNorm(); + if (l2 > l2min) + break; + } + if (j > ilast) { + assert(i <= ilast); + // If the last edge is too short, merge it with the previous edge. + i = ilast; + ptnext = contour.front().cast(); + } + + // Normal to the (ptnext - pt) segment. + Vec2d nnext = perp(ptnext - pt).normalized(); + + double delta = deltas[i]; + double sin_a = clamp(-1., 1., cross2(nprev, nnext)); + double convex = sin_a * delta; + if (convex <= - sin_min_parallel) { + // Concave corner. + add_offset_point(pt + nprev * delta); + add_offset_point(pt); + add_offset_point(pt + nnext * delta); + } else { + double dot = nprev.dot(nnext); + if (convex < sin_min_parallel && dot > 0.) { + // Nearly parallel. + add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt); + } else { + // Convex corner, possibly extremely sharp if convex < sin_min_parallel. + double r = 1. + dot; + if (r >= miter_limit) + add_offset_point(pt + (nprev + nnext) * (delta / r)); + else { + double dx = std::tan(std::atan2(sin_a, dot) / 4.); + Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta; + Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta; +#ifndef NDEBUG + Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt; + double dist_norm = vedge.norm(); + assert(std::abs(dist_norm - std::abs(delta)) < SCALED_EPSILON); +#endif /* NDEBUG */ + add_offset_point(newpt1); + add_offset_point(newpt2); + } + } + } + + if (i == ilast) + break; + + ptprev = pt; + nprev = nnext; + pt = ptnext; + i = j; + } + } + } + +#if 0 + { + ClipperLib::Path polytmp(out); + unscaleClipperPolygon(polytmp); + Slic3r::Polygon offsetted = ClipperPath_to_Slic3rPolygon(polytmp); + BoundingBox bbox = get_extents(contour); + bbox.merge(get_extents(offsetted)); + static int iRun = 0; + SVG svg(debug_out_path("mittered_offset_path_scaled-%d.svg", iRun ++).c_str(), bbox); + svg.draw_outline(Polygon(contour), "blue", scale_(0.01)); + svg.draw_outline(offsetted, "red", scale_(0.01)); + svg.draw(contour, "blue", scale_(0.03)); + svg.draw((Points)offsetted, "blue", scale_(0.03)); + } +#endif + + return out; +} + +Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) +{ +#ifndef NDEBUG + // Verify that the deltas are all non positive. + for (const std::vector &ds : deltas) + for (float delta : ds) + assert(delta <= 0.); + assert(expoly.holes.size() + 1 == deltas.size()); +#endif /* NDEBUG */ + + // 1) Offset the outer contour. + ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, true); + + // 2) Offset the holes one by one, collect the results. + ClipperLib::Paths holes; + holes.reserve(expoly.holes.size()); + for (const Polygon& hole : expoly.holes) + append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, false)); + + // 3) Subtract holes from the contours. + ClipperLib::Paths output; + if (holes.empty()) + output = std::move(contours); + else { + ClipperLib::Clipper clipper; + clipper.Clear(); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + } + + // 4) Unscale the output. + unscaleClipperPolygons(output); + return ClipperPaths_to_Slic3rPolygons(output); +} + +Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) +{ +#ifndef NDEBUG + // Verify that the deltas are all non positive. +for (const std::vector& ds : deltas) + for (float delta : ds) + assert(delta >= 0.); + assert(expoly.holes.size() + 1 == deltas.size()); +#endif /* NDEBUG */ + + // 1) Offset the outer contour. + ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false); + + // 2) Offset the holes one by one, collect the results. + ClipperLib::Paths holes; + holes.reserve(expoly.holes.size()); + for (const Polygon& hole : expoly.holes) + append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); + + // 3) Subtract holes from the contours. + ClipperLib::Paths output; + if (holes.empty()) + output = std::move(contours); + else { + ClipperLib::Clipper clipper; + clipper.Clear(); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + } + + // 4) Unscale the output. + unscaleClipperPolygons(output); + return ClipperPaths_to_Slic3rPolygons(output); +} + +ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) +{ +#ifndef NDEBUG + // Verify that the deltas are all non positive. +for (const std::vector& ds : deltas) + for (float delta : ds) + assert(delta >= 0.); + assert(expoly.holes.size() + 1 == deltas.size()); +#endif /* NDEBUG */ + + // 1) Offset the outer contour. + ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false); + + // 2) Offset the holes one by one, collect the results. + ClipperLib::Paths holes; + holes.reserve(expoly.holes.size()); + for (const Polygon& hole : expoly.holes) + append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); + + // 3) Subtract holes from the contours. + unscaleClipperPolygons(contours); + ExPolygons output; + if (holes.empty()) { + output.reserve(contours.size()); + for (ClipperLib::Path &path : contours) + output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); + } else { + ClipperLib::Clipper clipper; + unscaleClipperPolygons(holes); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + ClipperLib::PolyTree polytree; + clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + output = PolyTreeToExPolygons(polytree); + } + + return output; +} + + +ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) +{ +#ifndef NDEBUG + // Verify that the deltas are all non positive. +for (const std::vector& ds : deltas) + for (float delta : ds) + assert(delta <= 0.); + assert(expoly.holes.size() + 1 == deltas.size()); +#endif /* NDEBUG */ + + // 1) Offset the outer contour. + ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, false); + + // 2) Offset the holes one by one, collect the results. + ClipperLib::Paths holes; + holes.reserve(expoly.holes.size()); + for (const Polygon& hole : expoly.holes) + append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, true)); + + // 3) Subtract holes from the contours. + unscaleClipperPolygons(contours); + ExPolygons output; + if (holes.empty()) { + output.reserve(contours.size()); + for (ClipperLib::Path &path : contours) + output.emplace_back(ClipperPath_to_Slic3rPolygon(path)); + } else { + ClipperLib::Clipper clipper; + unscaleClipperPolygons(holes); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + ClipperLib::PolyTree polytree; + clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + output = PolyTreeToExPolygons(polytree); + } + + return output; +} + } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 0e58d7fac6..5a41a6a909 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -34,6 +34,7 @@ Slic3r::ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree); ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input); ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input); +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input); ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input); Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input); Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input); @@ -215,8 +216,19 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_ ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); +ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false); +ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false); +ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ = false); + Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); -void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); + +void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); +void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); +void traverse_pt(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval); + +void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval); +void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval); +void traverse_pt_unordered(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval); /* OTHER */ Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); @@ -226,6 +238,11 @@ void safety_offset(ClipperLib::Paths* paths); Polygons top_level_islands(const Slic3r::Polygons &polygons); +Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); +Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); +ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); +ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); + } #endif diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 9aab3a0eb7..9d24d8cb79 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -271,8 +271,6 @@ ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, Con return def; } -std::string ConfigOptionDef::nocli = "~~~noCLI"; - std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const { // prepare a function for wrapping text @@ -427,7 +425,30 @@ std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const return opt->serialize(); } -bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) +void ConfigBase::set(const std::string &opt_key, int value, bool create) +{ + ConfigOption *opt = this->option_throw(opt_key, create); + switch (opt->type()) { + case coInt: static_cast(opt)->value = value; break; + case coFloat: static_cast(opt)->value = value; break; + case coFloatOrPercent: static_cast(opt)->value = value; static_cast(opt)->percent = false; break; + case coString: static_cast(opt)->value = std::to_string(value); break; + default: throw BadOptionTypeException("Configbase::set() - conversion from int not possible"); + } +} + +void ConfigBase::set(const std::string &opt_key, double value, bool create) +{ + ConfigOption *opt = this->option_throw(opt_key, create); + switch (opt->type()) { + case coFloat: static_cast(opt)->value = value; break; + case coFloatOrPercent: static_cast(opt)->value = value; static_cast(opt)->percent = false; break; + case coString: static_cast(opt)->value = std::to_string(value); break; + default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible"); + } +} + +bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) { t_config_option_key opt_key = opt_key_src; std::string value = value_src; @@ -440,6 +461,18 @@ bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const s return this->set_deserialize_raw(opt_key, value, append); } +void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) +{ + if (! this->set_deserialize_nothrow(opt_key_src, value_src, append)) + throw BadOptionTypeException("ConfigBase::set_deserialize() failed"); +} + +void ConfigBase::set_deserialize(std::initializer_list items) +{ + for (const SetDeserializeItem &item : items) + this->set_deserialize(item.opt_key, item.opt_value, item.append); +} + bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append) { t_config_option_key opt_key = opt_key_src; @@ -670,6 +703,12 @@ void ConfigBase::null_nullables() } } +DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys) +{ + for (const t_config_option_key& opt_key : keys) + this->options[opt_key] = std::unique_ptr(rhs.option(opt_key)->clone()); +} + bool DynamicConfig::operator==(const DynamicConfig &rhs) const { auto it1 = this->options.begin(); @@ -819,7 +858,7 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, static_cast(opt_base)->value = value; } else { // Any scalar value of a type different from Bool and String. - if (! this->set_deserialize(opt_key, value, false)) { + if (! this->set_deserialize_nothrow(opt_key, value, false)) { boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; return false; } diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 334593ab5f..c49dd134ed 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -52,6 +52,16 @@ public: std::runtime_error(std::string("No definition exception: ") + opt_key) {} }; +/// Indicate that an unsupported accessor was called on a config option. +class BadOptionTypeException : public std::runtime_error +{ +public: + BadOptionTypeException() : + std::runtime_error("Bad option type exception") {} + BadOptionTypeException(const char* message) : + std::runtime_error(message) {} +}; + // Type of a configuration value. enum ConfigOptionType { coVectorType = 0x4000, @@ -117,10 +127,10 @@ public: virtual ConfigOption* clone() const = 0; // Set a value from a ConfigOption. The two options should be compatible. virtual void set(const ConfigOption *option) = 0; - virtual int getInt() const { throw std::runtime_error("Calling ConfigOption::getInt on a non-int ConfigOption"); } - virtual double getFloat() const { throw std::runtime_error("Calling ConfigOption::getFloat on a non-float ConfigOption"); } - virtual bool getBool() const { throw std::runtime_error("Calling ConfigOption::getBool on a non-boolean ConfigOption"); } - virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); } + virtual int getInt() const { throw BadOptionTypeException("Calling ConfigOption::getInt on a non-int ConfigOption"); } + virtual double getFloat() const { throw BadOptionTypeException("Calling ConfigOption::getFloat on a non-float ConfigOption"); } + virtual bool getBool() const { throw BadOptionTypeException("Calling ConfigOption::getBool on a non-boolean ConfigOption"); } + virtual void setInt(int /* val */) { throw BadOptionTypeException("Calling ConfigOption::setInt on a non-int ConfigOption"); } virtual bool operator==(const ConfigOption &rhs) const = 0; bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } @@ -1444,7 +1454,7 @@ public: std::vector cli_args(const std::string &key) const; // Assign this key to cli to disable CLI for this option. - static std::string nocli; + static const constexpr char *nocli = "~~~noCLI"; }; // Map from a config option name to its definition. @@ -1513,32 +1523,48 @@ protected: public: // Non-virtual methods: bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; } + const ConfigOption* option(const t_config_option_key &opt_key) const { return const_cast(this)->option(opt_key, false); } + ConfigOption* option(const t_config_option_key &opt_key, bool create = false) { return this->optptr(opt_key, create); } + template TYPE* option(const t_config_option_key &opt_key, bool create = false) { ConfigOption *opt = this->optptr(opt_key, create); return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast(opt); } + template const TYPE* option(const t_config_option_key &opt_key) const { return const_cast(this)->option(opt_key, false); } - template - TYPE* option_throw(const t_config_option_key &opt_key, bool create = false) + + ConfigOption* option_throw(const t_config_option_key &opt_key, bool create = false) { ConfigOption *opt = this->optptr(opt_key, create); if (opt == nullptr) throw UnknownOptionException(opt_key); + return opt; + } + + const ConfigOption* option_throw(const t_config_option_key &opt_key) const + { return const_cast(this)->option_throw(opt_key, false); } + + template + TYPE* option_throw(const t_config_option_key &opt_key, bool create = false) + { + ConfigOption *opt = this->option_throw(opt_key, create); if (opt->type() != TYPE::static_type()) - throw std::runtime_error("Conversion to a wrong type"); + throw BadOptionTypeException("Conversion to a wrong type"); return static_cast(opt); } + template const TYPE* option_throw(const t_config_option_key &opt_key) const { return const_cast(this)->option_throw(opt_key, false); } + // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. @@ -1551,9 +1577,40 @@ public: t_config_option_keys diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; std::string opt_serialize(const t_config_option_key &opt_key) const; + + // Set a value. Convert numeric types using a C style implicit conversion / promotion model. + // Throw if option is not avaiable and create is not enabled, + // or if the conversion is not possible. + // Conversion to string is always possible. + void set(const std::string &opt_key, bool value, bool create = false) + { this->option_throw(opt_key, create)->value = value; } + void set(const std::string &opt_key, int value, bool create = false); + void set(const std::string &opt_key, double value, bool create = false); + void set(const std::string &opt_key, const char *value, bool create = false) + { this->option_throw(opt_key, create)->value = value; } + void set(const std::string &opt_key, const std::string &value, bool create = false) + { this->option_throw(opt_key, create)->value = value; } + // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. - bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); + bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false); + // May throw BadOptionTypeException() if the operation fails. + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); + struct SetDeserializeItem { + SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} + SetDeserializeItem(const char *opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} + SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} + SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + std::string opt_key; std::string opt_value; bool append = false; + }; + // May throw BadOptionTypeException() if the operation fails. + void set_deserialize(std::initializer_list items); double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; @@ -1580,9 +1637,11 @@ class DynamicConfig : public virtual ConfigBase { public: DynamicConfig() {} - DynamicConfig(const DynamicConfig& other) { *this = other; } - DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } - virtual ~DynamicConfig() override { clear(); } + DynamicConfig(const DynamicConfig &rhs) { *this = rhs; } + DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); } + explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys); + explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {} + virtual ~DynamicConfig() override { clear(); } // Copy a content of one DynamicConfig to another DynamicConfig. // If rhs.def() is not null, then it has to be equal to this->def(). diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index a97210da63..065a69ba36 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -46,11 +46,29 @@ void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution) ++ ncontours; // Collect the contours. - m_contours.assign(ncontours, NULL); + m_contours.assign(ncontours, nullptr); ncontours = 0; for (size_t j = 0; j < polygons.size(); ++ j) if (! polygons[j].points.empty()) - m_contours[ncontours++] = &polygons[j].points; + m_contours[ncontours ++] = &polygons[j].points; + + create_from_m_contours(resolution); +} + +void EdgeGrid::Grid::create(const std::vector &polygons, coord_t resolution) +{ + // Count the contours. + size_t ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j].empty()) + ++ ncontours; + + // Collect the contours. + m_contours.assign(ncontours, nullptr); + ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j].empty()) + m_contours[ncontours ++] = &polygons[j]; create_from_m_contours(resolution); } @@ -66,7 +84,7 @@ void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution) ++ ncontours; // Collect the contours. - m_contours.assign(ncontours, NULL); + m_contours.assign(ncontours, nullptr); ncontours = 0; if (! expoly.contour.points.empty()) m_contours[ncontours++] = &expoly.contour.points; @@ -91,7 +109,7 @@ void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution) } // Collect the contours. - m_contours.assign(ncontours, NULL); + m_contours.assign(ncontours, nullptr); ncontours = 0; for (size_t i = 0; i < expolygons.size(); ++ i) { const ExPolygon &expoly = expolygons[i]; @@ -113,6 +131,7 @@ void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resol // m_contours has been initialized. Now fill in the edge grid. void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) { + assert(resolution > 0); // 1) Measure the bounding box. for (size_t i = 0; i < m_contours.size(); ++ i) { const Slic3r::Points &pts = *m_contours[i]; @@ -281,7 +300,11 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) Visitor(std::vector> &cell_data, std::vector &cells, size_t cols) : cell_data(cell_data), cells(cells), cols(cols), i(0), j(0) {} - void operator()(coord_t iy, coord_t ix) { cell_data[cells[iy*cols + ix].end++] = std::pair(i, j); } + inline bool operator()(coord_t iy, coord_t ix) { + cell_data[cells[iy*cols + ix].end++] = std::pair(i, j); + // Continue traversing the grid along the edge. + return true; + } std::vector> &cell_data; std::vector &cells; @@ -1017,8 +1040,139 @@ float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const return f; } - -bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const { + +EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt, coord_t search_radius) const +{ + BoundingBox bbox; + bbox.min = bbox.max = Point(pt(0) - m_bbox.min(0), pt(1) - m_bbox.min(1)); + bbox.defined = true; + // Upper boundary, round to grid and test validity. + bbox.max(0) += search_radius; + bbox.max(1) += search_radius; + ClosestPointResult result; + if (bbox.max(0) < 0 || bbox.max(1) < 0) + return result; + bbox.max(0) /= m_resolution; + bbox.max(1) /= m_resolution; + if ((size_t)bbox.max(0) >= m_cols) + bbox.max(0) = m_cols - 1; + if ((size_t)bbox.max(1) >= m_rows) + bbox.max(1) = m_rows - 1; + // Lower boundary, round to grid and test validity. + bbox.min(0) -= search_radius; + bbox.min(1) -= search_radius; + if (bbox.min(0) < 0) + bbox.min(0) = 0; + if (bbox.min(1) < 0) + bbox.min(1) = 0; + bbox.min(0) /= m_resolution; + bbox.min(1) /= m_resolution; + // Is the interval empty? + if (bbox.min(0) > bbox.max(0) || + bbox.min(1) > bbox.max(1)) + return result; + // Traverse all cells in the bounding box. + double d_min = double(search_radius); + // Signum of the distance field at pt. + int sign_min = 0; + double l2_seg_min = 1.; + for (int r = bbox.min(1); r <= bbox.max(1); ++ r) { + for (int c = bbox.min(0); c <= bbox.max(0); ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + for (size_t i = cell.begin; i < cell.end; ++ i) { + const size_t contour_idx = m_cell_data[i].first; + const Slic3r::Points &pts = *m_contours[contour_idx]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment. + const Slic3r::Point &p1 = pts[ipt]; + const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1]; + const Slic3r::Point v_seg = p2 - p1; + const Slic3r::Point v_pt = pt - p1; + // dot(p2-p1, pt-p1) + int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); + // l2 of seg + int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); + if (t_pt < 0) { + // Closest to p1. + double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); + if (dabs < d_min) { + // Previous point. + const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1]; + Slic3r::Point v_seg_prev = p1 - p0; + int64_t t2_pt = int64_t(v_seg_prev(0)) * int64_t(v_pt(0)) + int64_t(v_seg_prev(1)) * int64_t(v_pt(1)); + if (t2_pt > 0) { + // Inside the wedge between the previous and the next segment. + d_min = dabs; + // Set the signum depending on whether the vertex is convex or reflex. + int64_t det = int64_t(v_seg_prev(0)) * int64_t(v_seg(1)) - int64_t(v_seg_prev(1)) * int64_t(v_seg(0)); + assert(det != 0); + sign_min = (det > 0) ? 1 : -1; + result.contour_idx = contour_idx; + result.start_point_idx = ipt; + result.t = 0.; +#ifndef NDEBUG + Vec2d vfoot = (p1 - pt).cast(); + double dist_foot = vfoot.norm(); + double dist_foot_err = dist_foot - d_min; + assert(std::abs(dist_foot_err) < 1e-7 * d_min); +#endif /* NDEBUG */ + } + } + } + else if (t_pt > l2_seg) { + // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell. + continue; + } else { + // Closest to the segment. + assert(t_pt >= 0 && t_pt <= l2_seg); + int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); + double d = double(d_seg) / sqrt(double(l2_seg)); + double dabs = std::abs(d); + if (dabs < d_min) { + d_min = dabs; + sign_min = (d_seg < 0) ? -1 : ((d_seg == 0) ? 0 : 1); + l2_seg_min = l2_seg; + result.contour_idx = contour_idx; + result.start_point_idx = ipt; + result.t = t_pt; +#ifndef NDEBUG + Vec2d foot = p1.cast() * (1. - result.t / l2_seg_min) + p2.cast() * (result.t / l2_seg_min); + Vec2d vfoot = foot - pt.cast(); + double dist_foot = vfoot.norm(); + double dist_foot_err = dist_foot - d_min; + assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * d_min); +#endif /* NDEBUG */ + } + } + } + } + } + if (result.contour_idx != -1 && d_min <= double(search_radius)) { + result.distance = d_min * sign_min; + result.t /= l2_seg_min; + assert(result.t >= 0. && result.t < 1.); +#ifndef NDEBUG + { + const Slic3r::Points &pts = *m_contours[result.contour_idx]; + const Slic3r::Point &p1 = pts[result.start_point_idx]; + const Slic3r::Point &p2 = pts[(result.start_point_idx + 1 == pts.size()) ? 0 : result.start_point_idx + 1]; + Vec2d vfoot; + if (result.t == 0) + vfoot = p1.cast() - pt.cast(); + else + vfoot = p1.cast() * (1. - result.t) + p2.cast() * result.t - pt.cast(); + double dist_foot = vfoot.norm(); + double dist_foot_err = dist_foot - std::abs(result.distance); + assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * std::abs(result.distance)); + } +#endif /* NDEBUG */ + } else + result = ClosestPointResult(); + return result; +} + +bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const +{ BoundingBox bbox; bbox.min = bbox.max = Point(pt(0) - m_bbox.min(0), pt(1) - m_bbox.min(1)); bbox.defined = true; @@ -1047,7 +1201,7 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu bbox.min(1) > bbox.max(1)) return false; // Traverse all cells in the bounding box. - float d_min = search_radius; + double d_min = double(search_radius); // Signum of the distance field at pt. int sign_min = 0; bool on_segment = false; diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index cad20e07bb..1a51f3439c 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -21,10 +21,13 @@ public: void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; } void create(const Polygons &polygons, coord_t resolution); + void create(const std::vector &polygons, coord_t resolution); void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution); void create(const ExPolygonCollection &expolygons, coord_t resolution); + const std::vector& contours() const { return m_contours; } + #if 0 // Test, whether the edges inside the grid intersect with the polygons provided. bool intersect(const MultiPoint &polyline, bool closed); @@ -46,7 +49,19 @@ public: float signed_distance_bilinear(const Point &pt) const; // Calculate a signed distance to the contours in search_radius from the point. - bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = NULL) const; + struct ClosestPointResult { + size_t contour_idx = size_t(-1); + size_t start_point_idx = size_t(-1); + // Signed distance to the closest point. + double distance = std::numeric_limits::max(); + // Parameter of the closest point on edge starting with start_point_idx <0, 1) + double t = 0.; + + bool valid() const { return contour_idx != size_t(-1); } + }; + ClosestPointResult closest_point(const Point &pt, coord_t search_radius) const; + + bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = nullptr) const; // Calculate a signed distance to the contours in search_radius from the point. If no edge is found in search_radius, // return an interpolated value from m_signed_distance_field, if it exists. @@ -65,7 +80,7 @@ public: std::vector> intersecting_edges() const; bool has_intersecting_edges() const; - template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, FUNCTION func) const + template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, VISITOR &visitor) const { // End points of the line segment. p1(0) -= m_bbox.min(0); @@ -82,8 +97,7 @@ public: assert(ixb >= 0 && size_t(ixb) < m_cols); assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. - func(iy, ix); - if (ix == ixb && iy == iyb) + if (! visitor(iy, ix) || (ix == ixb && iy == iyb)) // Both ends fall into the same cell. return; // Raster the centeral part of the line. @@ -113,7 +127,8 @@ public: ey = int64_t(dx) * m_resolution; iy += 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } else { @@ -131,7 +146,8 @@ public: ey = int64_t(dx) * m_resolution; iy -= 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } } @@ -153,7 +169,8 @@ public: ey = int64_t(dx) * m_resolution; iy += 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } else { @@ -185,7 +202,8 @@ public: ey = int64_t(dx) * m_resolution; iy -= 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } } diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp new file mode 100644 index 0000000000..0f4eb01350 --- /dev/null +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -0,0 +1,416 @@ +#include "clipper/clipper_z.hpp" + +#include "libslic3r.h" +#include "ClipperUtils.hpp" +#include "EdgeGrid.hpp" +#include "ExPolygon.hpp" +#include "ElephantFootCompensation.hpp" +#include "Flow.hpp" +#include "Geometry.hpp" +#include "SVG.hpp" + +#include +#include + +// #define CONTOUR_DISTANCE_DEBUG_SVG + +namespace Slic3r { + +struct ResampledPoint { + ResampledPoint(size_t idx_src, bool interpolated, double curve_parameter) : idx_src(idx_src), interpolated(interpolated), curve_parameter(curve_parameter) {} + + size_t idx_src; + // Is this point interpolated or initial? + bool interpolated; + // Euclidean distance along the curve from the 0th point. + double curve_parameter; +}; + +std::vector contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector &resampled_point_parameters, double search_radius) +{ + assert(! contour.empty()); + assert(contour.size() >= 2); + + std::vector out; + + if (contour.size() > 2) + { +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + static int iRun = 0; + ++ iRun; + BoundingBox bbox = get_extents(contour); + bbox.merge(grid.bbox()); + ExPolygon expoly_grid; + expoly_grid.contour = Polygon(*grid.contours().front()); + for (size_t i = 1; i < grid.contours().size(); ++ i) + expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i])); +#endif + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector &resampled_point_parameters, double dist_same_contour_reject) : + grid(grid), idx_contour(idx_contour), resampled_point_parameters(resampled_point_parameters), dist_same_contour_reject(dist_same_contour_reject) {} + + void init(const size_t aidx_point_start, const Point &apt_start, Vec2d dir, const double radius) { + this->idx_point_start = aidx_point_start; + this->pt = apt_start.cast() + SCALED_EPSILON * dir; + dir *= radius; + this->pt_start = this->pt.cast(); + // Trim the vector by the grid's bounding box. + const BoundingBox &bbox = this->grid.bbox(); + double t = 1.; + for (size_t axis = 0; axis < 2; ++ axis) { + double dx = std::abs(dir(axis)); + if (dx >= EPSILON) { + double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - EPSILON); + if (tedge < dx) + t = tedge / dx; + } + } + this->dir = dir; + if (t < 1.) + dir *= t; + this->pt_end = (this->pt + dir).cast(); + this->t_min = 1.; + } + + bool operator()(coord_t iy, coord_t ix) { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = this->grid.cell_data_range(iy, ix); + bool valid = true; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = this->grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, this->pt_start, this->pt_end)) { + // The two segments intersect. Calculate the intersection. + Vec2d pt2 = segment.first.cast(); + Vec2d dir2 = segment.second.cast() - pt2; + Vec2d vptpt2 = pt - pt2; + double denom = dir(0) * dir2(1) - dir2(0) * dir(1); + + if (std::abs(denom) >= EPSILON) { + double t = cross2(dir2, vptpt2) / denom; + assert(t > - EPSILON && t < 1. + EPSILON); + bool this_valid = true; + if (it_contour_and_segment->first == idx_contour) { + // The intersected segment originates from the same contour as the starting point. + // Reject the intersection if it is close to the starting point. + // Find the start and end points of this segment + double param_lo = resampled_point_parameters[idx_point_start].curve_parameter; + double param_hi; + double param_end = resampled_point_parameters.back().curve_parameter; + { + const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first]; + size_t ipt = it_contour_and_segment->second; + ResampledPoint key(ipt, false, 0.); + auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); }; + auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower); + assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated); + double t2 = cross2(dir, vptpt2) / denom; + assert(t2 > - EPSILON && t2 < 1. + EPSILON); + if (++ ipt == ipts.size()) + param_hi = t2 * dir2.norm(); + else + param_hi = it->curve_parameter + t2 * dir2.norm(); + } + if (param_lo > param_hi) + std::swap(param_lo, param_hi); + assert(param_lo >= 0. && param_lo <= param_end); + assert(param_hi >= 0. && param_hi <= param_end); + this_valid = param_hi > param_lo + dist_same_contour_reject && param_hi - param_end < param_lo - dist_same_contour_reject; + } + if (t < this->t_min) { + this->t_min = t; + valid = this_valid; + } + } + } + if (! valid) + this->t_min = 1.; + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const size_t idx_contour; + const std::vector &resampled_point_parameters; + const double dist_same_contour_reject; + + size_t idx_point_start; + Point pt_start; + Point pt_end; + Vec2d pt; + Vec2d dir; + // Minium parameter along the vector (pt_end - pt_start). + double t_min; + } visitor(grid, idx_contour, resampled_point_parameters, search_radius); + + const Point *pt_this = &contour.back(); + size_t idx_pt_this = contour.size() - 1; + const Point *pt_prev = pt_this - 1; + // perpenduclar vector + auto perp = [](const Vec2d& v) -> Vec2d { return Vec2d(v.y(), -v.x()); }; + Vec2d vprev = (*pt_this - *pt_prev).cast().normalized(); + out.reserve(contour.size() + 1); + for (const Point &pt_next : contour) { + Vec2d vnext = (pt_next - *pt_this).cast().normalized(); + Vec2d dir = - (perp(vprev) + perp(vnext)).normalized(); + Vec2d dir_perp = perp(dir); + double cross = cross2(vprev, vnext); + double dot = vprev.dot(vnext); + double a = (cross < 0 || dot > 0.5) ? (M_PI / 3.) : (0.48 * acos(std::min(1., - dot))); + // Throw rays, collect distances. + std::vector distances; + int num_rays = 15; + +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox); + svg.draw(expoly_grid); + svg.draw_outline(Polygon(contour), "blue", scale_(0.01)); + svg.draw(*pt_this, "red", scale_(0.1)); +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + + for (int i = - num_rays + 1; i < num_rays; ++ i) { + double angle = a * i / (int)num_rays; + double c = cos(angle); + double s = sin(angle); + Vec2d v = c * dir + s * dir_perp; + visitor.init(idx_pt_this, *pt_this, v, search_radius); + grid.visit_cells_intersecting_line(visitor.pt_start, visitor.pt_end, visitor); + distances.emplace_back(visitor.t_min); +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01)); + if (visitor.t_min < 1.) { + Vec2d pt = visitor.pt + visitor.dir * visitor.t_min; + svg.draw(Point(pt), "red", scale_(0.1)); + } +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + } +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + svg.Close(); +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + std::sort(distances.begin(), distances.end()); +#if 0 + double median = distances[distances.size() / 2]; + double standard_deviation = 0; + for (double d : distances) + standard_deviation += (d - median) * (d - median); + standard_deviation = sqrt(standard_deviation / (distances.size() - 1)); + double avg = 0; + size_t cnt = 0; + for (double d : distances) + if (d > median - standard_deviation - EPSILON && d < median + standard_deviation + EPSILON) { + avg += d; + ++ cnt; + } + avg /= double(cnt); + out.emplace_back(float(avg * search_radius)); +#else + out.emplace_back(float(distances.front() * search_radius)); +#endif +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, &pt_next - contour.data(), unscale(out.back())); +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + pt_this = &pt_next; + idx_pt_this = &pt_next - contour.data(); + vprev = vnext; + } + // Rotate the vector by one item. + out.emplace_back(out.front()); + out.erase(out.begin()); + } + + return out; +} + +Points resample_polygon(const Points &contour, double dist, std::vector &resampled_point_parameters) +{ + Points out; + out.reserve(contour.size()); + resampled_point_parameters.reserve(contour.size()); + if (contour.size() > 2) { + Vec2d pt_prev = contour.back().cast(); + for (const Point &pt : contour) { + size_t idx_this = &pt - contour.data(); + const Vec2d pt_this = pt.cast(); + const Vec2d v = pt_this - pt_prev; + const double l = v.norm(); + const size_t n = size_t(ceil(l / dist)); + const double l_step = l / n; + for (size_t i = 1; i < n; ++ i) { + double interpolation_parameter = double(i) / n; + Vec2d new_pt = pt_prev + v * interpolation_parameter; + out.emplace_back(new_pt.cast()); + resampled_point_parameters.emplace_back(idx_this, true, l_step); + } + out.emplace_back(pt); + resampled_point_parameters.emplace_back(idx_this, false, l_step); + pt_prev = pt_this; + } + for (size_t i = 1; i < resampled_point_parameters.size(); ++i) + resampled_point_parameters[i].curve_parameter += resampled_point_parameters[i - 1].curve_parameter; + } + return out; +} + +static inline void smooth_compensation(std::vector &compensation, float strength, size_t num_iterations) +{ + std::vector out(compensation); + for (size_t iter = 0; iter < num_iterations; ++ iter) { + for (size_t i = 0; i < compensation.size(); ++ i) { + float prev = (i == 0) ? compensation.back() : compensation[i - 1]; + float next = (i + 1 == compensation.size()) ? compensation.front() : compensation[i + 1]; + float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next); + // Compensations are negative. Only apply the laplacian if it leads to lower compensation. + out[i] = std::max(laplacian, compensation[i]); + } + out.swap(compensation); + } +} + +template +static inline INDEX_TYPE prev_idx_cyclic(INDEX_TYPE idx, const CONTAINER &container) +{ + if (idx == 0) + idx = INDEX_TYPE(container.size()); + return -- idx; +} + +template +static inline INDEX_TYPE next_idx_cyclic(INDEX_TYPE idx, const CONTAINER &container) +{ + if (++ idx == INDEX_TYPE(container.size())) + idx = 0; + return idx; +} + +template +static inline T exchange(T& obj, U&& new_value) +{ + T old_value = std::move(obj); + obj = std::forward(new_value); + return old_value; +} + +static inline void smooth_compensation_banded(const Points &contour, float band, std::vector &compensation, float strength, size_t num_iterations) +{ + assert(contour.size() == compensation.size()); + assert(contour.size() > 2); + std::vector out(compensation); + float dist_min2 = band * band; + static constexpr bool use_min = false; + for (size_t iter = 0; iter < num_iterations; ++ iter) { + for (int i = 0; i < int(compensation.size()); ++ i) { + const Vec2f pthis = contour[i].cast(); + + int j = prev_idx_cyclic(i, contour); + Vec2f pprev = contour[j].cast(); + float prev = compensation[j]; + float l2 = (pthis - pprev).squaredNorm(); + if (l2 < dist_min2) { + float l = sqrt(l2); + int jprev = exchange(j, prev_idx_cyclic(j, contour)); + while (j != i) { + const Vec2f pp = contour[j].cast(); + const float lthis = (pp - pprev).norm(); + const float lnext = l + lthis; + if (lnext > band) { + // Interpolate the compensation value. + prev = use_min ? + std::min(prev, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) : + lerp(compensation[jprev], compensation[j], (band - l) / lthis); + break; + } + prev = use_min ? std::min(prev, compensation[j]) : compensation[j]; + pprev = pp; + l = lnext; + jprev = exchange(j, prev_idx_cyclic(j, contour)); + } + } + + j = next_idx_cyclic(i, contour); + pprev = contour[j].cast(); + float next = compensation[j]; + l2 = (pprev - pthis).squaredNorm(); + if (l2 < dist_min2) { + float l = sqrt(l2); + int jprev = exchange(j, next_idx_cyclic(j, contour)); + while (j != i) { + const Vec2f pp = contour[j].cast(); + const float lthis = (pp - pprev).norm(); + const float lnext = l + lthis; + if (lnext > band) { + // Interpolate the compensation value. + next = use_min ? + std::min(next, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) : + lerp(compensation[jprev], compensation[j], (band - l) / lthis); + break; + } + next = use_min ? std::min(next, compensation[j]) : compensation[j]; + pprev = pp; + l = lnext; + jprev = exchange(j, next_idx_cyclic(j, contour)); + } + } + + float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next); + // Compensations are negative. Only apply the laplacian if it leads to lower compensation. + out[i] = std::max(laplacian, compensation[i]); + } + out.swap(compensation); + } +} + +ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, const Flow &external_perimeter_flow, const double compensation) +{ + // The contour shall be wide enough to apply the external perimeter plus compensation on both sides. + double min_contour_width = double(external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing()); + double scaled_compensation = scale_(compensation); + double min_contour_width_compensated = min_contour_width + 2. * scaled_compensation; + // Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work. + double search_radius = min_contour_width_compensated + min_contour_width * 0.5; + + EdgeGrid::Grid grid; + ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front(); + BoundingBox bbox = get_extents(simplified.contour); + bbox.offset(SCALED_EPSILON); + grid.set_bbox(bbox); + grid.create(simplified, coord_t(0.7 * search_radius)); + std::vector> deltas; + deltas.reserve(simplified.holes.size() + 1); + ExPolygon resampled(simplified); + double resample_interval = scale_(0.5); + for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) { + Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1]; + std::vector resampled_point_parameters; + poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters); + std::vector dists = contour_distance(grid, idx_contour, poly.points, resampled_point_parameters, search_radius); + for (float &d : dists) { +// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale(d)); + // Convert contour width to available compensation distance. + if (d < min_contour_width) + d = 0.f; + else if (d > min_contour_width_compensated) + d = - float(scaled_compensation); + else + d = - (d - float(min_contour_width)) / 2.f; + assert(d >= - float(scaled_compensation) && d <= 0.f); + } +// smooth_compensation(dists, 0.4f, 10); + smooth_compensation_banded(poly.points, float(0.8 * resample_interval), dists, 0.3f, 3); + deltas.emplace_back(dists); + } + + ExPolygons out = variable_offset_inner_ex(resampled, deltas, 2.); + return out.front(); +} + +ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation) +{ + ExPolygons out; + out.reserve(input.size()); + for (const ExPolygon &expoly : input) + out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation)); + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/ElephantFootCompensation.hpp b/src/libslic3r/ElephantFootCompensation.hpp new file mode 100644 index 0000000000..0119df1af5 --- /dev/null +++ b/src/libslic3r/ElephantFootCompensation.hpp @@ -0,0 +1,16 @@ +#ifndef slic3r_ElephantFootCompensation_hpp_ +#define slic3r_ElephantFootCompensation_hpp_ + +#include "libslic3r.h" +#include + +namespace Slic3r { + +class Flow; + +ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation); +ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation); + +} // Slic3r + +#endif /* slic3r_ElephantFootCompensation_hpp_ */ diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index afbc0931e7..4ee8974f47 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -18,8 +18,18 @@ class ExPolygon { public: ExPolygon() {} - ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} + ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {} + explicit ExPolygon(const Polygon &contour) : contour(contour) {} + explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {} + explicit ExPolygon(const Points &contour) : contour(contour) {} + explicit ExPolygon(Points &&contour) : contour(std::move(contour)) {} + explicit ExPolygon(const Polygon &contour, const Polygon &hole) : contour(contour) { holes.emplace_back(hole); } + explicit ExPolygon(Polygon &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); } + explicit ExPolygon(const Points &contour, const Points &hole) : contour(contour) { holes.emplace_back(hole); } + explicit ExPolygon(Points &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); } + ExPolygon(std::initializer_list contour) : contour(contour) {} + ExPolygon(std::initializer_list contour, std::initializer_list hole) : contour(contour), holes({ hole }) {} ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; } ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } @@ -67,8 +77,16 @@ public: void triangulate_pp(Points *triangles) const; void triangulate_p2t(Polygons* polygons) const; Lines lines() const; + + // Number of contours (outer contour with holes). + size_t num_contours() const { return this->holes.size() + 1; } + Polygon& contour_or_hole(size_t idx) { return (idx == 0) ? this->contour : this->holes[idx - 1]; } + const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; } }; +inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; } +inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour != rhs.contour || lhs.holes != rhs.holes; } + // Count a nuber of polygons stored inside the vector of expolygons. // Useful for allocating space for polygons when converting expolygons to polygons. inline size_t number_polygons(const ExPolygons &expolys) @@ -293,6 +311,15 @@ inline bool expolygons_contain(ExPolygons &expolys, const Point &pt) return false; } +inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double tolerance) +{ + ExPolygons out; + out.reserve(expolys.size()); + for (const ExPolygon &exp : expolys) + exp.simplify(tolerance, &out); + return out; +} + extern BoundingBox get_extents(const ExPolygon &expolygon); extern BoundingBox get_extents(const ExPolygons &expolygons); extern BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); diff --git a/src/libslic3r/ExPolygonCollection.cpp b/src/libslic3r/ExPolygonCollection.cpp index 6933544b6f..c33df0f298 100644 --- a/src/libslic3r/ExPolygonCollection.cpp +++ b/src/libslic3r/ExPolygonCollection.cpp @@ -11,7 +11,7 @@ ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon) ExPolygonCollection::operator Points() const { Points points; - Polygons pp = *this; + Polygons pp = (Polygons)*this; for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) points.push_back(*point); diff --git a/src/libslic3r/ExPolygonCollection.hpp b/src/libslic3r/ExPolygonCollection.hpp index 4c181cd6a0..35e1eef4eb 100644 --- a/src/libslic3r/ExPolygonCollection.hpp +++ b/src/libslic3r/ExPolygonCollection.hpp @@ -13,15 +13,15 @@ typedef std::vector ExPolygonCollections; class ExPolygonCollection { - public: +public: ExPolygons expolygons; - ExPolygonCollection() {}; - ExPolygonCollection(const ExPolygon &expolygon); - ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}; - operator Points() const; - operator Polygons() const; - operator ExPolygons&(); + ExPolygonCollection() {} + explicit ExPolygonCollection(const ExPolygon &expolygon); + explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {} + explicit operator Points() const; + explicit operator Polygons() const; + explicit operator ExPolygons&(); void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 7f57b78af4..c0d08c84b7 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -14,12 +14,12 @@ namespace Slic3r { void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(intersection_pl(this->polyline, collection), retval); + this->_inflate_collection(intersection_pl(this->polyline, (Polygons)collection), retval); } void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(diff_pl(this->polyline, collection), retval); + this->_inflate_collection(diff_pl(this->polyline, (Polygons)collection), retval); } void ExtrusionPath::clip_end(double distance) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index ce52ae1526..b22d85b657 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -5,6 +5,8 @@ #include "Polygon.hpp" #include "Polyline.hpp" +#include + namespace Slic3r { class ExPolygonCollection; @@ -12,7 +14,7 @@ class ExtrusionEntityCollection; class Extruder; // Each ExtrusionRole value identifies a distinct set of { extruder, speed } -enum ExtrusionRole { +enum ExtrusionRole : uint8_t { erNone, erPerimeter, erExternalPerimeter, @@ -79,8 +81,8 @@ public: virtual ExtrusionEntity* clone_move() = 0; virtual ~ExtrusionEntity() {} virtual void reverse() = 0; - virtual Point first_point() const = 0; - virtual Point last_point() const = 0; + virtual const Point& first_point() const = 0; + virtual const Point& last_point() const = 0; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0; @@ -115,30 +117,23 @@ public: float width; // Height of the extrusion, used for visualization purposes. float height; - // Feedrate of the extrusion, used for visualization purposes. - float feedrate; - // Id of the extruder, used for visualization purposes. - unsigned int extruder_id; - // Id of the color, used for visualization purposes in the color printing case. - unsigned int cp_color_id; - ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} - ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} - ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} - ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} - ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} - ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} -// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {}; + ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}; + ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}; + ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} + ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} + ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} + ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} - ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = rhs.polyline; return *this; } - ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = std::move(rhs.polyline); return *this; } + ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; } + ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; } ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); } void reverse() override { this->polyline.reverse(); } - Point first_point() const override { return this->polyline.points.front(); } - Point last_point() const override { return this->polyline.points.back(); } + const Point& first_point() const override { return this->polyline.points.front(); } + const Point& last_point() const override { return this->polyline.points.back(); } size_t size() const { return this->polyline.size(); } bool empty() const { return this->polyline.empty(); } bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } @@ -198,8 +193,8 @@ public: // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); } void reverse() override; - Point first_point() const override { return this->paths.front().polyline.points.front(); } - Point last_point() const override { return this->paths.back().polyline.points.back(); } + const Point& first_point() const override { return this->paths.front().polyline.points.front(); } + const Point& last_point() const override { return this->paths.back().polyline.points.back(); } double length() const override; ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. @@ -241,8 +236,8 @@ public: bool make_clockwise(); bool make_counter_clockwise(); void reverse() override; - Point first_point() const override { return this->paths.front().polyline.points.front(); } - Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } + const Point& first_point() const override { return this->paths.front().polyline.points.front(); } + const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } Polygon polygon() const; double length() const override; bool split_at_vertex(const Point &point); diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 70c2348afa..e1a9709d1a 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -1,4 +1,5 @@ #include "ExtrusionEntityCollection.hpp" +#include "ShortestPath.hpp" #include #include #include @@ -16,7 +17,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE this->entities = other.entities; for (size_t i = 0; i < this->entities.size(); ++i) this->entities[i] = this->entities[i]->clone(); - this->orig_indices = other.orig_indices; this->no_sort = other.no_sort; return *this; } @@ -24,7 +24,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) { std::swap(this->entities, c.entities); - std::swap(this->orig_indices, c.orig_indices); std::swap(this->no_sort, c.no_sort); } @@ -75,79 +74,31 @@ void ExtrusionEntityCollection::remove(size_t i) this->entities.erase(this->entities.begin() + i); } -ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const +ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const { - ExtrusionEntityCollection coll; - this->chained_path(&coll, no_reverse, role); - return coll; -} - -void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector* orig_indices) const -{ - if (this->entities.empty()) return; - this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices); -} - -ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const -{ - ExtrusionEntityCollection coll; - this->chained_path_from(start_near, &coll, no_reverse, role); - return coll; -} - -void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector* orig_indices) const -{ - if (this->no_sort) { - *retval = *this; - return; - } - - retval->entities.reserve(this->entities.size()); - retval->orig_indices.reserve(this->entities.size()); - - // if we're asked to return the original indices, build a map - std::map indices_map; - - ExtrusionEntitiesPtr my_paths; - for (ExtrusionEntity * const &entity_src : this->entities) { - if (role != erMixed) { - // The caller wants only paths with a specific extrusion role. - auto role2 = entity_src->role(); - if (role != role2) { - // This extrusion entity does not match the role asked. - assert(role2 != erMixed); - continue; - } - } - - ExtrusionEntity *entity = entity_src->clone(); - my_paths.push_back(entity); - if (orig_indices != nullptr) - indices_map[entity] = &entity_src - &this->entities.front(); - } - - Points endpoints; - for (const ExtrusionEntity *entity : my_paths) { - endpoints.push_back(entity->first_point()); - endpoints.push_back((no_reverse || ! entity->can_reverse()) ? - entity->first_point() : entity->last_point()); - } - - while (! my_paths.empty()) { - // find nearest point - int start_index = start_near.nearest_point_index(endpoints); - int path_index = start_index/2; - ExtrusionEntity* entity = my_paths.at(path_index); - // never reverse loops, since it's pointless for chained path and callers might depend on orientation - if (start_index % 2 && !no_reverse && entity->can_reverse()) - entity->reverse(); - retval->entities.push_back(my_paths.at(path_index)); - if (orig_indices != nullptr) - orig_indices->push_back(indices_map[entity]); - my_paths.erase(my_paths.begin() + path_index); - endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); - start_near = retval->entities.back()->last_point(); - } + ExtrusionEntityCollection out; + if (this->no_sort) { + out = *this; + } else { + if (role == erMixed) + out = *this; + else { + for (const ExtrusionEntity *ee : this->entities) { + if (role != erMixed) { + // The caller wants only paths with a specific extrusion role. + auto role2 = ee->role(); + if (role != role2) { + // This extrusion entity does not match the role asked. + assert(role2 != erMixed); + continue; + } + } + out.entities.emplace_back(ee->clone()); + } + } + chain_and_reorder_extrusion_entities(out.entities, &start_near); + } + return out; } void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const @@ -175,18 +126,26 @@ size_t ExtrusionEntityCollection::items_count() const } // Returns a single vector of pointers to all non-collection items contained in this one. -ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const +ExtrusionEntityCollection ExtrusionEntityCollection::flatten(bool preserve_ordering) const { struct Flatten { + Flatten(bool preserve_ordering) : preserve_ordering(preserve_ordering) {} ExtrusionEntityCollection out; + bool preserve_ordering; void recursive_do(const ExtrusionEntityCollection &collection) { - for (const ExtrusionEntity* entity : collection.entities) - if (entity->is_collection()) - this->recursive_do(*static_cast(entity)); - else - out.append(*entity); + if (collection.no_sort && preserve_ordering) { + // Don't flatten whatever happens below this level. + out.append(collection); + } else { + for (const ExtrusionEntity *entity : collection.entities) + if (entity->is_collection()) + this->recursive_do(*static_cast(entity)); + else + out.append(*entity); + } } - } flatten; + } flatten(preserve_ordering); + flatten.recursive_do(*this); return flatten.out; } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 221afc4536..3084e5741a 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -14,19 +14,18 @@ public: ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); } ExtrusionEntitiesPtr entities; // we own these entities - std::vector orig_indices; // handy for XS bool no_sort; - ExtrusionEntityCollection(): no_sort(false) {}; - ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); } - ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {} + ExtrusionEntityCollection(): no_sort(false) {} + ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); } + ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {} explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); - ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) - { this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; } + ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) + { this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; } ~ExtrusionEntityCollection() { clear(); } explicit operator ExtrusionPaths() const; - bool is_collection() const { return true; }; + bool is_collection() const { return true; } ExtrusionRole role() const override { ExtrusionRole out = erNone; for (const ExtrusionEntity *ee : entities) { @@ -35,8 +34,8 @@ public: } return out; } - bool can_reverse() const { return !this->no_sort; }; - bool empty() const { return this->entities.empty(); }; + bool can_reverse() const { return !this->no_sort; } + bool empty() const { return this->entities.empty(); } void clear(); void swap (ExtrusionEntityCollection &c); void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); } @@ -66,13 +65,10 @@ public: } void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); - ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const; - void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector* orig_indices = nullptr) const; - ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const; - void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector* orig_indices = nullptr) const; + ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const; void reverse(); - Point first_point() const { return this->entities.front()->first_point(); } - Point last_point() const { return this->entities.back()->last_point(); } + const Point& first_point() const { return this->entities.front()->first_point(); } + const Point& last_point() const { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; @@ -85,7 +81,10 @@ public: Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } size_t items_count() const; - ExtrusionEntityCollection flatten() const; + /// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections. + /// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy). + /// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False). + ExtrusionEntityCollection flatten(bool preserve_ordering = false) const; double min_mm3_per_mm() const; double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } diff --git a/src/libslic3r/Fill/Fill.hpp b/src/libslic3r/Fill/Fill.hpp index c04305c042..9e35450849 100644 --- a/src/libslic3r/Fill/Fill.hpp +++ b/src/libslic3r/Fill/Fill.hpp @@ -29,8 +29,6 @@ public: FillParams params; }; -void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out); - } // namespace Slic3r #endif // slic3r_Fill_hpp_ diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 6a37e4369f..6c4b4d9036 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -1,5 +1,5 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include "Fill3DHoneycomb.hpp" @@ -158,46 +158,18 @@ void Fill3DHoneycomb::_fill_surface_single( ((this->layer_id/thickness_layers) % 2) + 1); // move pattern in place - for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) - it->translate(bb.min(0), bb.min(1)); + for (Polyline &pl : polylines) + pl.translate(bb.min); - // clip pattern to boundaries - polylines = intersection_pl(polylines, (Polygons)expolygon); + // clip pattern to boundaries, chain the clipped polylines + Polylines polylines_chained = chain_polylines(intersection_pl(polylines, to_polygons(expolygon))); - // connect lines - if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections - ExPolygon expolygon_off; - { - ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON); - if (! expolygons_off.empty()) { - // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. - assert(expolygons_off.size() == 1); - std::swap(expolygon_off, expolygons_off.front()); - } - } - Polylines chained = PolylineCollection::chained_path_from( - std::move(polylines), - PolylineCollection::leftmost_point(polylines), false); // reverse allowed - bool first = true; - for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { - if (! first) { - // Try to connect the lines. - Points &pts_end = polylines_out.back().points; - const Point &first_point = it_polyline->points.front(); - const Point &last_point = pts_end.back(); - // TODO: we should also check that both points are on a fill_boundary to avoid - // connecting paths on the boundaries of internal regions - if ((last_point - first_point).cast().norm() <= 1.5 * distance && - expolygon_off.contains(Line(last_point, first_point))) { - // Append the polyline. - pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); - continue; - } - } - // The lines cannot be connected. - polylines_out.emplace_back(std::move(*it_polyline)); - first = false; - } + // connect lines if needed + if (! polylines_chained.empty()) { + if (params.dont_connect) + append(polylines_out, std::move(polylines_chained)); + else + this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params); } } diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c16d764152..0ba75465f2 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -1,8 +1,10 @@ #include #include "../ClipperUtils.hpp" +#include "../EdgeGrid.hpp" #include "../Surface.hpp" #include "../PrintConfig.hpp" +#include "../libslic3r.h" #include "FillBase.hpp" #include "FillConcentric.hpp" @@ -148,4 +150,814 @@ std::pair Fill::_infill_direction(const Surface *surface) const return std::pair(out_angle, out_shift); } +#if 0 +// From pull request "Gyroid improvements" #2730 by @supermerill + +/// cut poly between poly.point[idx_1] & poly.point[idx_1+1] +/// add p1+-width to one part and p2+-width to the other one. +/// add the "new" polyline to polylines (to part cut from poly) +/// p1 & p2 have to be between poly.point[idx_1] & poly.point[idx_1+1] +/// if idx_1 is ==0 or == size-1, then we don't need to create a new polyline. +static void cut_polyline(Polyline &poly, Polylines &polylines, size_t idx_1, Point p1, Point p2) { + //reorder points + if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) { + Point temp = p2; + p2 = p1; + p1 = temp; + } + if (idx_1 == poly.points.size() - 1) { + //shouldn't be possible. + poly.points.erase(poly.points.end() - 1); + } else { + // create new polyline + Polyline new_poly; + //put points in new_poly + new_poly.points.push_back(p2); + new_poly.points.insert(new_poly.points.end(), poly.points.begin() + idx_1 + 1, poly.points.end()); + //erase&put points in poly + poly.points.erase(poly.points.begin() + idx_1 + 1, poly.points.end()); + poly.points.push_back(p1); + //safe test + if (poly.length() == 0) + poly.points = new_poly.points; + else + polylines.emplace_back(new_poly); + } +} + +/// the poly is like a polygon but with first_point != last_point (already removed) +static void cut_polygon(Polyline &poly, size_t idx_1, Point p1, Point p2) { + //reorder points + if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) { + Point temp = p2; + p2 = p1; + p1 = temp; + } + //check if we need to rotate before cutting + if (idx_1 != poly.size() - 1) { + //put points in new_poly + poly.points.insert(poly.points.end(), poly.points.begin(), poly.points.begin() + idx_1 + 1); + poly.points.erase(poly.points.begin(), poly.points.begin() + idx_1 + 1); + } + //put points in poly + poly.points.push_back(p1); + poly.points.insert(poly.points.begin(), p2); +} + +/// check if the polyline from pts_to_check may be at 'width' distance of a point in polylines_blocker +/// it use equally_spaced_points with width/2 precision, so don't worry with pts_to_check number of points. +/// it use the given polylines_blocker points, be sure to put enough of them to be reliable. +/// complexity : N(pts_to_check.equally_spaced_points(width / 2)) x N(polylines_blocker.points) +static bool collision(const Points &pts_to_check, const Polylines &polylines_blocker, const coordf_t width) { + //check if it's not too close to a polyline + coordf_t min_dist_square = width * width * 0.9 - SCALED_EPSILON; + Polyline better_polylines(pts_to_check); + Points better_pts = better_polylines.equally_spaced_points(width / 2); + for (const Point &p : better_pts) { + for (const Polyline &poly2 : polylines_blocker) { + for (const Point &p2 : poly2.points) { + if (p.distance_to_square(p2) < min_dist_square) { + return true; + } + } + } + } + return false; +} + +/// Try to find a path inside polylines that allow to go from p1 to p2. +/// width if the width of the extrusion +/// polylines_blockers are the array of polylines to check if the path isn't blocked by something. +/// complexity: N(polylines.points) + a collision check after that if we finded a path: N(2(p2-p1)/width) x N(polylines_blocker.points) +static Points get_frontier(Polylines &polylines, const Point& p1, const Point& p2, const coord_t width, const Polylines &polylines_blockers, coord_t max_size = -1) { + for (size_t idx_poly = 0; idx_poly < polylines.size(); ++idx_poly) { + Polyline &poly = polylines[idx_poly]; + if (poly.size() <= 1) continue; + + //loop? + if (poly.first_point() == poly.last_point()) { + //polygon : try to find a line for p1 & p2. + size_t idx_11, idx_12, idx_21, idx_22; + idx_11 = poly.closest_point_index(p1); + idx_12 = idx_11; + if (Line(poly.points[idx_11], poly.points[(idx_11 + 1) % (poly.points.size() - 1)]).distance_to(p1) < SCALED_EPSILON) { + idx_12 = (idx_11 + 1) % (poly.points.size() - 1); + } else if (Line(poly.points[(idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2)], poly.points[idx_11]).distance_to(p1) < SCALED_EPSILON) { + idx_11 = (idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2); + } else { + continue; + } + idx_21 = poly.closest_point_index(p2); + idx_22 = idx_21; + if (Line(poly.points[idx_21], poly.points[(idx_21 + 1) % (poly.points.size() - 1)]).distance_to(p2) < SCALED_EPSILON) { + idx_22 = (idx_21 + 1) % (poly.points.size() - 1); + } else if (Line(poly.points[(idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2)], poly.points[idx_21]).distance_to(p2) < SCALED_EPSILON) { + idx_21 = (idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2); + } else { + continue; + } + + + //edge case: on the same line + if (idx_11 == idx_21 && idx_12 == idx_22) { + if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points(); + //break loop + poly.points.erase(poly.points.end() - 1); + cut_polygon(poly, idx_11, p1, p2); + return Points() = { Line(p1, p2).midpoint() }; + } + + //compute distance & array for the ++ path + Points ret_1_to_2; + double dist_1_to_2 = p1.distance_to(poly.points[idx_12]); + ret_1_to_2.push_back(poly.points[idx_12]); + size_t max = idx_12 <= idx_21 ? idx_21+1 : poly.points.size(); + for (size_t i = idx_12 + 1; i < max; i++) { + dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]); + ret_1_to_2.push_back(poly.points[i]); + } + if (idx_12 > idx_21) { + dist_1_to_2 += poly.points.back().distance_to(poly.points.front()); + ret_1_to_2.push_back(poly.points[0]); + for (size_t i = 1; i <= idx_21; i++) { + dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]); + ret_1_to_2.push_back(poly.points[i]); + } + } + dist_1_to_2 += p2.distance_to(poly.points[idx_21]); + + //compute distance & array for the -- path + Points ret_2_to_1; + double dist_2_to_1 = p1.distance_to(poly.points[idx_11]); + ret_2_to_1.push_back(poly.points[idx_11]); + size_t min = idx_22 <= idx_11 ? idx_22 : 0; + for (size_t i = idx_11; i > min; i--) { + dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]); + ret_2_to_1.push_back(poly.points[i - 1]); + } + if (idx_22 > idx_11) { + dist_2_to_1 += poly.points.back().distance_to(poly.points.front()); + ret_2_to_1.push_back(poly.points[poly.points.size() - 1]); + for (size_t i = poly.points.size() - 1; i > idx_22; i--) { + dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]); + ret_2_to_1.push_back(poly.points[i - 1]); + } + } + dist_2_to_1 += p2.distance_to(poly.points[idx_22]); + + if (max_size < dist_2_to_1 && max_size < dist_1_to_2) { + return Points(); + } + + //choose between the two direction (keep the short one) + if (dist_1_to_2 < dist_2_to_1) { + if (collision(ret_1_to_2, polylines_blockers, width)) return Points(); + //break loop + poly.points.erase(poly.points.end() - 1); + //remove points + if (idx_12 <= idx_21) { + poly.points.erase(poly.points.begin() + idx_12, poly.points.begin() + idx_21 + 1); + if (idx_12 != 0) { + cut_polygon(poly, idx_11, p1, p2); + } //else : already cut at the good place + } else { + poly.points.erase(poly.points.begin() + idx_12, poly.points.end()); + poly.points.erase(poly.points.begin(), poly.points.begin() + idx_21); + cut_polygon(poly, poly.points.size() - 1, p1, p2); + } + return ret_1_to_2; + } else { + if (collision(ret_2_to_1, polylines_blockers, width)) return Points(); + //break loop + poly.points.erase(poly.points.end() - 1); + //remove points + if (idx_22 <= idx_11) { + poly.points.erase(poly.points.begin() + idx_22, poly.points.begin() + idx_11 + 1); + if (idx_22 != 0) { + cut_polygon(poly, idx_21, p1, p2); + } //else : already cut at the good place + } else { + poly.points.erase(poly.points.begin() + idx_22, poly.points.end()); + poly.points.erase(poly.points.begin(), poly.points.begin() + idx_11); + cut_polygon(poly, poly.points.size() - 1, p1, p2); + } + return ret_2_to_1; + } + } else { + //polyline : try to find a line for p1 & p2. + size_t idx_1, idx_2; + idx_1 = poly.closest_point_index(p1); + if (idx_1 < poly.points.size() - 1 && Line(poly.points[idx_1], poly.points[idx_1 + 1]).distance_to(p1) < SCALED_EPSILON) { + } else if (idx_1 > 0 && Line(poly.points[idx_1 - 1], poly.points[idx_1]).distance_to(p1) < SCALED_EPSILON) { + idx_1 = idx_1 - 1; + } else { + continue; + } + idx_2 = poly.closest_point_index(p2); + if (idx_2 < poly.points.size() - 1 && Line(poly.points[idx_2], poly.points[idx_2 + 1]).distance_to(p2) < SCALED_EPSILON) { + } else if (idx_2 > 0 && Line(poly.points[idx_2 - 1], poly.points[idx_2]).distance_to(p2) < SCALED_EPSILON) { + idx_2 = idx_2 - 1; + } else { + continue; + } + + //edge case: on the same line + if (idx_1 == idx_2) { + if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points(); + cut_polyline(poly, polylines, idx_1, p1, p2); + return Points() = { Line(p1, p2).midpoint() }; + } + + //create ret array + size_t first_idx = idx_1; + size_t last_idx = idx_2 + 1; + if (idx_1 > idx_2) { + first_idx = idx_2; + last_idx = idx_1 + 1; + } + Points p_ret; + p_ret.insert(p_ret.end(), poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx); + + coordf_t length = 0; + for (size_t i = 1; i < p_ret.size(); i++) length += p_ret[i - 1].distance_to(p_ret[i]); + + if (max_size < length) { + return Points(); + } + + if (collision(p_ret, polylines_blockers, width)) return Points(); + //cut polyline + poly.points.erase(poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx); + cut_polyline(poly, polylines, first_idx, p1, p2); + //order the returned array to be p1->p2 + if (idx_1 > idx_2) { + std::reverse(p_ret.begin(), p_ret.end()); + } + return p_ret; + } + + } + + return Points(); +} + +/// Connect the infill_ordered polylines, in this order, from the back point to the next front point. +/// It uses only the boundary polygons to do so, and can't pass two times at the same place. +/// It avoid passing over the infill_ordered's polylines (preventing local over-extrusion). +/// return the connected polylines in polylines_out. Can output polygons (stored as polylines with first_point = last_point). +/// complexity: worst: N(infill_ordered.points) x N(boundary.points) +/// typical: N(infill_ordered) x ( N(boundary.points) + N(infill_ordered.points) ) +void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms) { + + //TODO: fallback to the quick & dirty old algorithm when n(points) is too high. + Polylines polylines_frontier = to_polylines(((Polygons)boundary)); + + Polylines polylines_blocker; + coord_t clip_size = scale_(this->spacing) * 2; + for (const Polyline &polyline : infill_ordered) { + if (polyline.length() > 2.01 * clip_size) { + polylines_blocker.push_back(polyline); + polylines_blocker.back().clip_end(clip_size); + polylines_blocker.back().clip_start(clip_size); + } + } + + //length between two lines + coordf_t ideal_length = (1 / params.density) * this->spacing; + + Polylines polylines_connected_first; + bool first = true; + for (const Polyline &polyline : infill_ordered) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_connected_first.back().points; + const Point &last_point = pts_end.back(); + const Point &first_point = polyline.points.front(); + if (last_point.distance_to(first_point) < scale_(this->spacing) * 10) { + Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker, (coord_t)scale_(ideal_length) * 2); + if (!pts_frontier.empty()) { + // The lines can be connected. + pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); + continue; + } + } + } + // The lines cannot be connected. + polylines_connected_first.emplace_back(std::move(polyline)); + + first = false; + } + + Polylines polylines_connected; + first = true; + for (const Polyline &polyline : polylines_connected_first) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_connected.back().points; + const Point &last_point = pts_end.back(); + const Point &first_point = polyline.points.front(); + + Polylines before = polylines_frontier; + Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker); + if (!pts_frontier.empty()) { + // The lines can be connected. + pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); + continue; + } + } + // The lines cannot be connected. + polylines_connected.emplace_back(std::move(polyline)); + + first = false; + } + + //try to link to nearest point if possible + for (size_t idx1 = 0; idx1 < polylines_connected.size(); idx1++) { + size_t min_idx = 0; + coordf_t min_length = 0; + bool switch_id1 = false; + bool switch_id2 = false; + for (size_t idx2 = idx1 + 1; idx2 < polylines_connected.size(); idx2++) { + double last_first = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].first_point()); + double first_first = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].first_point()); + double first_last = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].last_point()); + double last_last = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].last_point()); + double min = std::min(std::min(last_first, last_last), std::min(first_first, first_last)); + if (min < min_length || min_length == 0) { + min_idx = idx2; + switch_id1 = (std::min(last_first, last_last) > std::min(first_first, first_last)); + switch_id2 = (std::min(last_first, first_first) > std::min(last_last, first_last)); + min_length = min; + } + } + if (min_idx > idx1 && min_idx < polylines_connected.size()){ + Points pts_frontier = get_frontier(polylines_frontier, + switch_id1 ? polylines_connected[idx1].first_point() : polylines_connected[idx1].last_point(), + switch_id2 ? polylines_connected[min_idx].last_point() : polylines_connected[min_idx].first_point(), + scale_(this->spacing), polylines_blocker); + if (!pts_frontier.empty()) { + if (switch_id1) polylines_connected[idx1].reverse(); + if (switch_id2) polylines_connected[min_idx].reverse(); + Points &pts_end = polylines_connected[idx1].points; + pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); + pts_end.insert(pts_end.end(), polylines_connected[min_idx].points.begin(), polylines_connected[min_idx].points.end()); + polylines_connected.erase(polylines_connected.begin() + min_idx); + } + } + } + + //try to create some loops if possible + for (Polyline &polyline : polylines_connected) { + Points pts_frontier = get_frontier(polylines_frontier, polyline.last_point(), polyline.first_point(), scale_(this->spacing), polylines_blocker); + if (!pts_frontier.empty()) { + polyline.points.insert(polyline.points.end(), pts_frontier.begin(), pts_frontier.end()); + polyline.points.insert(polyline.points.begin(), polyline.points.back()); + } + polylines_out.emplace_back(polyline); + } +} + +#else + +struct ContourPointData { + ContourPointData(float param) : param(param) {} + // Eucleidean position of the contour point along the contour. + float param = 0.f; + // Was the segment starting with this contour point extruded? + bool segment_consumed = false; + // Was this point extruded over? + bool point_consumed = false; +}; + +// Verify whether the contour from point idx_start to point idx_end could be taken (whether all segments along the contour were not yet extruded). +static bool could_take(const std::vector &contour_data, size_t idx_start, size_t idx_end) +{ + for (size_t i = idx_start; i < idx_end; ) { + if (contour_data[i].segment_consumed || contour_data[i].point_consumed) + return false; + if (++ i == contour_data.size()) + i = 0; + } + return ! contour_data[idx_end].point_consumed; +} + +// Connect end of pl1 to the start of pl2 using the perimeter contour. +// The idx_start and idx_end are ordered so that the connecting polyline points will be taken with increasing indices. +static void take(Polyline &pl1, Polyline &&pl2, const Points &contour, std::vector &contour_data, size_t idx_start, size_t idx_end, bool reversed) +{ +#ifndef NDEBUG + size_t num_points_initial = pl1.points.size(); + assert(idx_start != idx_end); +#endif /* NDEBUG */ + + { + // Reserve memory at pl1 for the connecting contour and pl2. + int new_points = int(idx_end) - int(idx_start) - 1; + if (new_points < 0) + new_points += int(contour.size()); + pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size()); + } + + contour_data[idx_start].point_consumed = true; + contour_data[idx_start].segment_consumed = true; + contour_data[idx_end ].point_consumed = true; + + if (reversed) { + size_t i = (idx_end == 0) ? contour_data.size() - 1 : idx_end - 1; + while (i != idx_start) { + contour_data[i].point_consumed = true; + contour_data[i].segment_consumed = true; + pl1.points.emplace_back(contour[i]); + if (i == 0) + i = contour_data.size(); + -- i; + } + } else { + size_t i = idx_start; + if (++ i == contour_data.size()) + i = 0; + while (i != idx_end) { + contour_data[i].point_consumed = true; + contour_data[i].segment_consumed = true; + pl1.points.emplace_back(contour[i]); + if (++ i == contour_data.size()) + i = 0; + } + } + + append(pl1.points, std::move(pl2.points)); +} + +// Return an index of start of a segment and a point of the clipping point at distance from the end of polyline. +struct SegmentPoint { + // Segment index, defining a line ::max(); + // Parameter of point in <0, 1) along the line ::max(); } +}; + +static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, double distance) +{ + assert(polyline.size() >= 2); + assert(distance > 0.); + // Initialized to "invalid". + SegmentPoint out; + if (polyline.size() >= 2) { + const double d2 = distance * distance; + Vec2d pt_prev = polyline.front().cast(); + for (int i = 1; i < polyline.size(); ++ i) { + Vec2d pt = polyline[i].cast(); + Vec2d v = pt - pt_prev; + double l2 = v.squaredNorm(); + if (l2 > d2) { + out.idx_segment = i; + out.t = distance / sqrt(l2); + out.point = pt + out.t * v; + break; + } + distance -= sqrt(l2); + pt_prev = pt; + } + } + return out; +} + +static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, double distance) +{ + assert(polyline.size() >= 2); + assert(distance > 0.); + // Initialized to "invalid". + SegmentPoint out; + if (polyline.size() >= 2) { + const double d2 = distance * distance; + Vec2d pt_next = polyline.back().cast(); + for (int i = int(polyline.size()) - 2; i >= 0; -- i) { + Vec2d pt = polyline[i].cast(); + Vec2d v = pt - pt_next; + double l2 = v.squaredNorm(); + if (l2 > d2) { + out.idx_segment = i; + out.t = distance / sqrt(l2); + out.point = pt + out.t * v; + break; + } + distance -= sqrt(l2); + pt_next = pt; + } + } + return out; +} + +static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2) +{ + const Vec2d v = p1b - p1a; + const Vec2d va = p2 - p1a; + const double l2 = v.squaredNorm(); + if (l2 < EPSILON) + // p1a == p1b + return va.squaredNorm(); + // Project p2 onto the (p1a, p1b) segment. + const double t = va.dot(v); + if (t < 0.) + return va.squaredNorm(); + else if (t > l2) + return (p2 - p1b).squaredNorm(); + return ((t / l2) * v - va).squaredNorm(); +} + +// Distance to the closest point of line. +static inline double min_distance_of_segments(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2a, const Vec2d &p2b) +{ + Vec2d v1 = p1b - p1a; + double l1_2 = v1.squaredNorm(); + if (l1_2 < EPSILON) + // p1a == p1b: Return distance of p1a from the (p2a, p2b) segment. + return segment_point_distance_squared(p2a, p2b, p1a); + + Vec2d v2 = p2b - p2a; + double l2_2 = v2.squaredNorm(); + if (l2_2 < EPSILON) + // p2a == p2b: Return distance of p2a from the (p1a, p1b) segment. + return segment_point_distance_squared(p1a, p1b, p2a); + + // Project p2a, p2b onto the (p1a, p1b) segment. + auto project_p2a_p2b_onto_seg_p1a_p1b = [](const Vec2d& p1a, const Vec2d& p1b, const Vec2d& p2a, const Vec2d& p2b, const Vec2d& v1, const double l1_2) { + Vec2d v1a2a = p2a - p1a; + Vec2d v1a2b = p2b - p1a; + double t1 = v1a2a.dot(v1); + double t2 = v1a2b.dot(v1); + if (t1 <= 0.) { + if (t2 <= 0.) + // Both p2a and p2b are left of v1. + return (((t1 < t2) ? p2b : p2a) - p1a).squaredNorm(); + else if (t2 < l1_2) + // Project p2b onto the (p1a, p1b) segment. + return ((t2 / l1_2) * v1 - v1a2b).squaredNorm(); + } + else if (t1 >= l1_2) { + if (t2 >= l1_2) + // Both p2a and p2b are right of v1. + return (((t1 < t2) ? p2a : p2b) - p1b).squaredNorm(); + else if (t2 < l1_2) + // Project p2b onto the (p1a, p1b) segment. + return ((t2 / l1_2) * v1 - v1a2b).squaredNorm(); + } + else { + // Project p1b onto the (p1a, p1b) segment. + double dist_min = ((t2 / l1_2) * v1 - v1a2a).squaredNorm(); + if (t2 > 0. && t2 < l1_2) + dist_min = std::min(dist_min, ((t2 / l1_2) * v1 - v1a2b).squaredNorm()); + return dist_min; + } + return std::numeric_limits::max(); + }; + + return std::min( + project_p2a_p2b_onto_seg_p1a_p1b(p1a, p1b, p2a, p2b, v1, l1_2), + project_p2a_p2b_onto_seg_p1a_p1b(p2a, p2b, p1a, p1b, v2, l2_2)); +} + +// Mark the segments of split boundary as consumed if they are very close to some of the infill line. +void mark_boundary_segments_touching_infill( + const std::vector &boundary, + std::vector> &boundary_data, + const BoundingBox &boundary_bbox, + const Polylines &infill, + const double clip_distance, + const double distance_colliding) +{ + EdgeGrid::Grid grid; + grid.set_bbox(boundary_bbox); + // Inflate the bounding box by a thick line width. + grid.create(boundary, clip_distance + scale_(10.)); + + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const std::vector &boundary, std::vector> &boundary_data, const double dist2_max) : + grid(grid), boundary(boundary), boundary_data(boundary_data), dist2_max(dist2_max) {} + + void init(const Vec2d &pt1, const Vec2d &pt2) { + this->pt1 = &pt1; + this->pt2 = &pt2; + } + + bool operator()(coord_t iy, coord_t ix) { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = this->grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = this->grid.segment(*it_contour_and_segment); + const Vec2d seg_pt1 = segment.first.cast(); + const Vec2d seg_pt2 = segment.second.cast(); + if (min_distance_of_segments(seg_pt1, seg_pt2, *this->pt1, *this->pt2) < this->dist2_max) { + // Mark this boundary segment as touching the infill line. + ContourPointData&bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second]; + bdp.segment_consumed = true; + // There is no need for checking seg_pt2 as it will be checked the next time. + if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max) + bdp.point_consumed = true; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const std::vector &boundary; + std::vector> &boundary_data; + // Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line. + const double dist2_max; + + const Vec2d *pt1; + const Vec2d *pt2; + } visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding); + + for (const Polyline &polyline : infill) { + // Clip the infill polyline by the Eucledian distance along the polyline. + SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance); + SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance); + if (start_point.valid() && end_point.valid() && + (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) { + // The clipped polyline is non-empty. + for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) { +//FIXME extend the EdgeGrid to suport tracing a thick line. +#if 0 + Point pt1, pt2; + Vec2d pt1d, pt2d; + if (point_idx == start_point.idx_segment) { + pt1d = start_point.point; + pt1 = pt1d.cast(); + } else { + pt1 = polyline.points[point_idx]; + pt1d = pt1.cast(); + } + if (point_idx == start_point.idx_segment) { + pt2d = end_point.point; + pt2 = pt1d.cast(); + } else { + pt2 = polyline.points[point_idx]; + pt2d = pt2.cast(); + } + visitor.init(pt1d, pt2d); + grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor); +#else + Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx].cast(); + Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx].cast(); + visitor.init(pt1, pt2); + // Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size. + Vec2d v = (pt2 - pt1).normalized() * distance_colliding; + Vec2d vperp(-v.y(), v.x()); + Vec2d a = pt1 - v - vperp; + Vec2d b = pt1 + v - vperp; + grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); + a = pt1 - v + vperp; + b = pt1 + v + vperp; + grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); +#endif + } + } + } +} + +void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const FillParams ¶ms) +{ + assert(! infill_ordered.empty()); + assert(! boundary_src.contour.points.empty()); + + BoundingBox bbox = get_extents(boundary_src.contour); + bbox.offset(SCALED_EPSILON); + + // 1) Add the end points of infill_ordered to boundary_src. + std::vector boundary; + std::vector> boundary_data; + boundary.assign(boundary_src.holes.size() + 1, Points()); + boundary_data.assign(boundary_src.holes.size() + 1, std::vector()); + // Mapping the infill_ordered end point to a (contour, point) of boundary. + std::vector> map_infill_end_point_to_boundary; + map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(std::numeric_limits::max(), std::numeric_limits::max())); + { + // Project the infill_ordered end points onto boundary_src. + std::vector> intersection_points; + { + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(boundary_src, scale_(10.)); + intersection_points.reserve(infill_ordered.size() * 2); + for (const Polyline &pl : infill_ordered) + for (const Point *pt : { &pl.points.front(), &pl.points.back() }) { + EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point(*pt, SCALED_EPSILON); + if (cp.valid()) { + // The infill end point shall lie on the contour. + assert(cp.distance < 2.); + intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1)); + } + } + std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) { + return cp1.first.contour_idx < cp2.first.contour_idx || + (cp1.first.contour_idx == cp2.first.contour_idx && + (cp1.first.start_point_idx < cp2.first.start_point_idx || + (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t))); + }); + } + auto it = intersection_points.begin(); + auto it_end = intersection_points.end(); + for (size_t idx_contour = 0; idx_contour <= boundary_src.holes.size(); ++ idx_contour) { + const Polygon &contour_src = (idx_contour == 0) ? boundary_src.contour : boundary_src.holes[idx_contour - 1]; + Points &contour_dst = boundary[idx_contour]; + for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) { + contour_dst.emplace_back(contour_src.points[idx_point]); + for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { + // Add these points to the destination contour. + const Vec2d pt1 = contour_src[idx_point].cast(); + const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); + const Vec2d pt = lerp(pt1, pt2, it->first.t); + map_infill_end_point_to_boundary[it->second] = std::make_pair(idx_contour, contour_dst.size()); + contour_dst.emplace_back(pt.cast()); + } + } + // Parametrize the curve. + std::vector &contour_data = boundary_data[idx_contour]; + contour_data.reserve(contour_dst.size()); + contour_data.emplace_back(ContourPointData(0.f)); + for (size_t i = 1; i < contour_dst.size(); ++ i) + contour_data.emplace_back(contour_data.back().param + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm()); + contour_data.front().param = contour_data.back().param + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); + } + +#ifndef NDEBUG + assert(boundary.size() == boundary_src.num_contours()); + assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), + [&boundary](const std::pair &contour_point) { + return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size(); + })); +#endif /* NDEBUG */ + } + + // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. + { + const double clip_distance = scale_(this->spacing); + const double distance_colliding = scale_(this->spacing); + mark_boundary_segments_touching_infill(boundary, boundary_data, bbox, infill_ordered, clip_distance, distance_colliding); + } + + // Chain infill_ordered. + //FIXME run the following loop through a heap sorted by the shortest perimeter edge that could be taken. + //length between two lines + //const float length_max = scale_(this->spacing); + const float length_max = scale_((2. / params.density) * this->spacing); + size_t idx_chain_last = 0; + for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { + Polyline &pl1 = infill_ordered[idx_chain_last]; + Polyline &pl2 = infill_ordered[idx_chain]; + const std::pair *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; + const std::pair *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; + const Points &contour = boundary[cp1->first]; + std::vector &contour_data = boundary_data[cp1->first]; + bool valid = false; + bool reversed = false; + if (cp1->first == cp2->first) { + // End points on the same contour. Try to connect them. + float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param; + float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param; + float param_end = contour_data.front().param; + if (param_lo > param_hi) { + std::swap(param_lo, param_hi); + std::swap(cp1, cp2); + reversed = true; + } + assert(param_lo >= 0.f && param_lo <= param_end); + assert(param_hi >= 0.f && param_hi <= param_end); + float dist1 = param_hi - param_lo; + float dist2 = param_lo + param_end - param_hi; + if (dist1 > dist2) { + std::swap(dist1, dist2); + std::swap(cp1, cp2); + reversed = ! reversed; + } + if (dist1 < length_max) { + // Try to connect the shorter path. + valid = could_take(contour_data, cp1->second, cp2->second); + // Try to connect the longer path. + if (! valid && dist2 < length_max) { + std::swap(cp1, cp2); + reversed = ! reversed; + valid = could_take(contour_data, cp1->second, cp2->second); + } + } + } + if (valid) + take(pl1, std::move(pl2), contour, contour_data, cp1->second, cp2->second, reversed); + else if (++ idx_chain_last < idx_chain) + infill_ordered[idx_chain_last] = std::move(pl2); + } + infill_ordered.erase(infill_ordered.begin() + idx_chain_last + 1, infill_ordered.end()); + append(polylines_out, std::move(infill_ordered)); +} + +#endif + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index fb79478930..d005f6e4ad 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -15,6 +15,7 @@ namespace Slic3r { +class ExPolygon; class Surface; struct FillParams @@ -110,6 +111,8 @@ protected: virtual std::pair _infill_direction(const Surface *surface) const; + void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms); + public: static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 04319bb268..e7b4706acd 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -1,5 +1,5 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include #include @@ -31,19 +31,26 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool static inline Polyline make_wave( const std::vector& one_period, double width, double height, double offset, double scaleFactor, - double z_cos, double z_sin, bool vertical) + double z_cos, double z_sin, bool vertical, bool flip) { std::vector points = one_period; double period = points.back()(0); - points.pop_back(); - int n = points.size(); - do { - points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1))); - } while (points.back()(0) < width); - points.back()(0) = width; + if (width != period) // do not extend if already truncated + { + points.reserve(one_period.size() * floor(width / period)); + points.pop_back(); + + int n = points.size(); + do { + points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1))); + } while (points.back()(0) < width - EPSILON); + + points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip))); + } // and construct the final polyline to return: Polyline polyline; + polyline.points.reserve(points.size()); for (auto& point : points) { point(1) += offset; point(1) = clamp(0., height, double(point(1))); @@ -55,45 +62,56 @@ static inline Polyline make_wave( return polyline; } -static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) +static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance) { std::vector points; - double dx = M_PI_4; // very coarse spacing to begin with + double dx = M_PI_2; // exact coordinates on main inflexion lobes double limit = std::min(2*M_PI, width); - for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too - x = std::min(x, limit); - points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip))); - } + points.reserve(ceil(limit / tolerance / 3)); - // now we will check all internal points and in case some are too far from the line connecting its neighbours, - // we will add one more point on each side: - const double tolerance = .1; - for (unsigned int i=1;i(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm(); - if (dist_mm > tolerance) { // if the difference from straight line is more than this - double x = 0.5f * (points[i-1](0) + points[i](0)); - points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); - x = 0.5f * (points[i+1](0) + points[i](0)); - points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); - // we added the points to the end, but need them all in order - std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; }); - // decrement i so we also check the first newly added point - --i; + for (double x = 0.; x < limit - EPSILON; x += dx) { + points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); + } + points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip))); + + // piecewise increase in resolution up to requested tolerance + for(;;) + { + size_t size = points.size(); + for (unsigned int i = 1;i < size; ++i) { + auto& lp = points[i-1]; // left point + auto& rp = points[i]; // right point + double x = lp(0) + (rp(0) - lp(0)) / 2; + double y = f(x, z_sin, z_cos, vertical, flip); + Vec2d ip = {x, y}; + if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) { + points.emplace_back(std::move(ip)); + } + } + + if (size == points.size()) + break; + else + { + // insert new points in order + std::sort(points.begin(), points.end(), + [](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); }); } } + return points; } static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) { const double scaleFactor = scale_(line_spacing) / density_adjusted; - //scale factor for 5% : 8 712 388 - // 1z = 10^-6 mm ? + + // tolerance in scaled units. clamp the maximum tolerance as there's + // no processing-speed benefit to do so beyond a certain point + const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale(scaleFactor); + + //scale factor for 5% : 8 712 388 + // 1z = 10^-6 mm ? const double z = gridZ / scaleFactor; const double z_sin = sin(z); const double z_cos = cos(z); @@ -109,16 +127,20 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double std::swap(width,height); } - std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time + std::vector one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time + flip = !flip; // even polylines are a bit shifted + std::vector one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); Polylines result; - for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines - result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); - - flip = !flip; // even polylines are a bit shifted - one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample - for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines - result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); + for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) { + // creates odd polylines + result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); + // creates even polylines + y0 += M_PI; + if (y0 < upper_bound + EPSILON) { + result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); + } + } return result; } @@ -130,66 +152,49 @@ void FillGyroid::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - // no rotation is supported for this infill pattern (yet) + float infill_angle = this->angle + (CorrectionAngle * 2*M_PI) / 360.; + if(abs(infill_angle) >= EPSILON) + expolygon.rotate(-infill_angle); + BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = std::max(0., params.density * 2.44); + double density_adjusted = std::max(0., params.density * DensityAdjust); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module - bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance))); + bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); // generate pattern - Polylines polylines = make_gyroid_waves( + Polylines polylines_square = make_gyroid_waves( scale_(this->z), density_adjusted, this->spacing, ceil(bb.size()(0) / distance) + 1., ceil(bb.size()(1) / distance) + 1.); - - // move pattern in place - for (Polyline &polyline : polylines) - polyline.translate(bb.min(0), bb.min(1)); - // clip pattern to boundaries - polylines = intersection_pl(polylines, (Polygons)expolygon); + // shift the polyline to the grid origin + for (Polyline &pl : polylines_square) + pl.translate(bb.min); - // connect lines - if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections - ExPolygon expolygon_off; - { - ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON); - if (! expolygons_off.empty()) { - // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. - assert(expolygons_off.size() == 1); - std::swap(expolygon_off, expolygons_off.front()); - } - } - Polylines chained = PolylineCollection::chained_path_from( - std::move(polylines), - PolylineCollection::leftmost_point(polylines), false); // reverse allowed - bool first = true; - for (Polyline &polyline : chained) { - if (! first) { - // Try to connect the lines. - Points &pts_end = polylines_out.back().points; - const Point &first_point = polyline.points.front(); - const Point &last_point = pts_end.back(); - // TODO: we should also check that both points are on a fill_boundary to avoid - // connecting paths on the boundaries of internal regions - // TODO: avoid crossing current infill path - if ((last_point - first_point).cast().norm() <= 5 * distance && - expolygon_off.contains(Line(last_point, first_point))) { - // Append the polyline. - pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); - continue; - } - } - // The lines cannot be connected. - polylines_out.emplace_back(std::move(polyline)); - first = false; - } + Polylines polylines_chained = chain_polylines(intersection_pl(polylines_square, to_polygons(expolygon))); + + size_t polylines_out_first_idx = polylines_out.size(); + if (! polylines_chained.empty()) { + // connect lines + if (params.dont_connect) + append(polylines_out, std::move(polylines_chained)); + else + this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params); + // remove too small bits (larger than longer) + polylines_out.erase( + std::remove_if(polylines_out.begin() + polylines_out_first_idx, polylines_out.end(), [this](const Polyline &pl){ return pl.length() < scale_(this->spacing * 3); }), + polylines_out.end()); + // new paths must be rotated back + if (abs(infill_angle) >= EPSILON) { + for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it) + it->rotate(infill_angle); + } } } diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp index 9c3cef940b..37babb25e3 100644 --- a/src/libslic3r/Fill/FillGyroid.hpp +++ b/src/libslic3r/Fill/FillGyroid.hpp @@ -16,6 +16,17 @@ public: // require bridge flow since most of this pattern hangs in air virtual bool use_bridge_flow() const { return false; } + // Correction applied to regular infill angle to maximize printing + // speed in default configuration (degrees) + static constexpr float CorrectionAngle = -45.; + + // Density adjustment to have a good %of weight. + static constexpr double DensityAdjust = 2.44; + + // Gyroid upper resolution tolerance (mm^-2) + static constexpr double PatternTolerance = 0.2; + + protected: virtual void _fill_surface_single( const FillParams ¶ms, diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index cbfe926f2f..948af182bb 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -1,5 +1,5 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include "FillHoneycomb.hpp" @@ -93,22 +93,20 @@ void FillHoneycomb::_fill_surface_single( // connect paths if (! paths.empty()) { // prevent calling leftmost_point() on empty collections - Polylines chained = PolylineCollection::chained_path_from( - std::move(paths), - PolylineCollection::leftmost_point(paths), false); + Polylines chained = chain_polylines(std::move(paths)); assert(paths.empty()); paths.clear(); - for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { + for (Polyline &path : chained) { if (! paths.empty()) { // distance between first point of this path and last point of last path - double distance = (it_path->first_point() - paths.back().last_point()).cast().norm(); + double distance = (path.first_point() - paths.back().last_point()).cast().norm(); if (distance <= m.hex_width) { - paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); + paths.back().points.insert(paths.back().points.end(), path.points.begin(), path.points.end()); continue; } } // Don't connect the paths. - paths.push_back(*it_path); + paths.push_back(std::move(path)); } } diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 3b9266a0fe..7a322ce991 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -1,5 +1,4 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" #include "../Surface.hpp" #include "FillPlanePath.hpp" diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 205eb1b669..629e5b6f46 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1,6 +1,6 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include "FillRectilinear.hpp" @@ -92,15 +92,12 @@ void FillRectilinear::_fill_surface_single( std::swap(expolygon_off, expolygons_off.front()); } } - Polylines chained = PolylineCollection::chained_path_from( - std::move(polylines), - PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; - for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + for (Polyline &polyline : chain_polylines(std::move(polylines))) { if (! first) { // Try to connect the lines. Points &pts_end = polylines_out.back().points; - const Point &first_point = it_polyline->points.front(); + const Point &first_point = polyline.points.front(); const Point &last_point = pts_end.back(); // Distance in X, Y. const Vector distance = last_point - first_point; @@ -109,12 +106,12 @@ void FillRectilinear::_fill_surface_single( if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) && expolygon_off.contains(Line(last_point, first_point))) { // Append the polyline. - pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); continue; } } // The lines cannot be connected. - polylines_out.emplace_back(std::move(*it_polyline)); + polylines_out.emplace_back(std::move(polyline)); first = false; } } diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 0f89e61930..ff3cf777d3 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -3,6 +3,9 @@ #include "../Utils.hpp" #include "../GCode.hpp" #include "../Geometry.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "../GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include "../I18N.hpp" @@ -31,7 +34,8 @@ namespace pt = boost::property_tree; // VERSION NUMBERS // 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. // 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. -const unsigned int VERSION_3MF = 1; +// 2 : Meshes saved in their local system; Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file. +const unsigned int VERSION_3MF = 2; const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file const std::string MODEL_FOLDER = "3D/"; @@ -39,6 +43,9 @@ const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; const std::string RELATIONSHIPS_FILE = "_rels/.rels"; +#if ENABLE_THUMBNAIL_GENERATOR +const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png"; +#endif // ENABLE_THUMBNAIL_GENERATOR const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; 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"; @@ -87,6 +94,13 @@ const char* VOLUME_TYPE = "volume"; const char* NAME_KEY = "name"; const char* MODIFIER_KEY = "modifier"; const char* VOLUME_TYPE_KEY = "volume_type"; +const char* MATRIX_KEY = "matrix"; +const char* SOURCE_FILE_KEY = "source_file"; +const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; +const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; +const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; +const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; +const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; const unsigned int VALID_OBJECT_TYPES_COUNT = 1; const char* VALID_OBJECT_TYPES[] = @@ -148,11 +162,15 @@ bool get_attribute_value_bool(const char** attributes, unsigned int attributes_s return (text != nullptr) ? (bool)::atoi(text) : true; } -Slic3r::Transform3d get_transform_from_string(const std::string& mat_str) +Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str) { + // check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md + // to see how matrices are stored inside 3mf according to specifications + Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); + if (mat_str.empty()) // empty string means default identity matrix - return Slic3r::Transform3d::Identity(); + return ret; std::vector mat_elements_str; boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on); @@ -160,9 +178,8 @@ Slic3r::Transform3d get_transform_from_string(const std::string& mat_str) unsigned int size = (unsigned int)mat_elements_str.size(); if (size != 12) // invalid data, return identity matrix - return Slic3r::Transform3d::Identity(); + return ret; - Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); unsigned int i = 0; // matrices are stored into 3mf files as 4x3 // we need to transpose them @@ -1375,7 +1392,7 @@ namespace Slic3r { bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) { int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); IdToModelObjectMap::iterator object_item = m_objects.find(object_id); if (object_item == m_objects.end()) @@ -1421,7 +1438,7 @@ namespace Slic3r { // see specifications int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); return _create_object_instance(object_id, transform, printable, 1); @@ -1634,6 +1651,21 @@ namespace Slic3r { return false; } + Slic3r::Geometry::Transformation transform; + if (m_version > 1) + { + // extract the volume transformation from the volume's metadata, if present + for (const Metadata& metadata : volume_data.metadata) + { + if (metadata.key == MATRIX_KEY) + { + transform.set_from_string(metadata.value); + break; + } + } + } + Transform3d inv_matrix = transform.get_matrix().inverse(); + // splits volume out of imported geometry TriangleMesh triangle_mesh; stl_file &stl = triangle_mesh.stl; @@ -1651,7 +1683,12 @@ namespace Slic3r { stl_facet& facet = stl.facet_start[i]; for (unsigned int v = 0; v < 3; ++v) { - ::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float)); + unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3; + Vec3f vertex(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); + if (m_version > 1) + // revert the vertices to the original mesh reference system + vertex = (inv_matrix * vertex.cast()).cast(); + ::memcpy(facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float)); } } @@ -1659,10 +1696,12 @@ namespace Slic3r { triangle_mesh.repair(); ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); - volume->center_geometry_after_creation(); + // apply the volume matrix taken from the metadata, if present + if (m_version > 1) + volume->set_transformation(transform); volume->calculate_convex_hull(); - // apply volume's name and config data + // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { if (metadata.key == NAME_KEY) @@ -1671,6 +1710,18 @@ namespace Slic3r { volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); else if (metadata.key == VOLUME_TYPE_KEY) volume->set_type(ModelVolume::type_from_string(metadata.value)); + else if (metadata.key == SOURCE_FILE_KEY) + volume->source.input_file = metadata.value; + else if (metadata.key == SOURCE_OBJECT_ID_KEY) + volume->source.object_idx = ::atoi(metadata.value.c_str()); + else if (metadata.key == SOURCE_VOLUME_ID_KEY) + volume->source.volume_idx = ::atoi(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_X_KEY) + volume->source.mesh_offset(0) = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_Y_KEY) + volume->source.mesh_offset(1) = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_Z_KEY) + volume->source.mesh_offset(2) = ::atof(metadata.value.c_str()); else volume->config.set_deserialize(metadata.key, metadata.value); } @@ -1761,11 +1812,22 @@ namespace Slic3r { typedef std::map IdToObjectDataMap; public: +#if ENABLE_THUMBNAIL_GENERATOR + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr); +#else bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config); +#endif // ENABLE_THUMBNAIL_GENERATOR private: +#if ENABLE_THUMBNAIL_GENERATOR + bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); +#else bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config); +#endif // ENABLE_THUMBNAIL_GENERATOR bool _add_content_types_file_to_archive(mz_zip_archive& archive); +#if ENABLE_THUMBNAIL_GENERATOR + bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR bool _add_relationships_file_to_archive(mz_zip_archive& archive); bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data); bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); @@ -1778,13 +1840,25 @@ namespace Slic3r { bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); }; +#if ENABLE_THUMBNAIL_GENERATOR + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) + { + clear_errors(); + return _save_model_to_file(filename, model, config, thumbnail_data); + } +#else bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config) { clear_errors(); return _save_model_to_file(filename, model, config); } +#endif // ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_THUMBNAIL_GENERATOR + bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) +#else bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config) +#endif // ENABLE_THUMBNAIL_GENERATOR { mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -1803,6 +1877,19 @@ namespace Slic3r { return false; } +#if ENABLE_THUMBNAIL_GENERATOR + if ((thumbnail_data != nullptr) && thumbnail_data->is_valid()) + { + // Adds the file Metadata/thumbnail.png. + if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data)) + { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR + // Adds relationships file ("_rels/.rels"). // The content of this file is the same for each PrusaSlicer 3mf. // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. @@ -1896,6 +1983,9 @@ namespace Slic3r { stream << "\n"; stream << " \n"; stream << " \n"; +#if ENABLE_THUMBNAIL_GENERATOR + stream << " \n"; +#endif // ENABLE_THUMBNAIL_GENERATOR stream << ""; std::string out = stream.str(); @@ -1909,12 +1999,35 @@ namespace Slic3r { return true; } +#if ENABLE_THUMBNAIL_GENERATOR + bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data) + { + bool res = false; + + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION); + mz_free(png_data); + } + + if (!res) + add_error("Unable to add thumbnail file to archive"); + + return res; + } +#endif // ENABLE_THUMBNAIL_GENERATOR + bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive) { std::stringstream stream; stream << "\n"; stream << "\n"; stream << " \n"; +#if ENABLE_THUMBNAIL_GENERATOR + stream << " \n"; +#endif // ENABLE_THUMBNAIL_GENERATOR stream << ""; std::string out = stream.str(); @@ -2116,7 +2229,7 @@ namespace Slic3r { for (const BuildItem& item : build_items) { - stream << " <" << ITEM_TAG << " objectid=\"" << item.id << "\" transform =\""; + stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\""; for (unsigned c = 0; c < 4; ++c) { for (unsigned r = 0; r < 3; ++r) @@ -2126,7 +2239,7 @@ namespace Slic3r { stream << " "; } } - stream << "\" printable =\"" << item.printable << "\" />\n"; + stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\" />\n"; } stream << " \n"; @@ -2344,6 +2457,31 @@ namespace Slic3r { stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; + // stores volume's local matrix + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; + const Transform3d& matrix = volume->get_matrix(); + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + stream << matrix(r, c); + if ((r != 3) || (c != 3)) + stream << " "; + } + } + stream << "\"/>\n"; + + // stores volume's source data + if (!volume->source.input_file.empty()) + { + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; + } + // stores volume's config data for (const std::string& key : volume->config.keys()) { @@ -2383,13 +2521,21 @@ namespace Slic3r { return res; } +#if ENABLE_THUMBNAIL_GENERATOR + bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) +#else bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config) +#endif // ENABLE_THUMBNAIL_GENERATOR { if ((path == nullptr) || (model == nullptr)) return false; _3MF_Exporter exporter; +#if ENABLE_THUMBNAIL_GENERATOR + bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data); +#else bool res = exporter.save_model_to_file(path, *model, config); +#endif // ENABLE_THUMBNAIL_GENERATOR if (!res) exporter.log_errors(); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index f387192ab5..2e85b7f59e 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -22,13 +22,20 @@ namespace Slic3r { class Model; class DynamicPrintConfig; +#if ENABLE_THUMBNAIL_GENERATOR + struct ThumbnailData; +#endif // ENABLE_THUMBNAIL_GENERATOR // Load the content of a 3mf file into the given model and preset bundle. extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices +#if ENABLE_THUMBNAIL_GENERATOR + extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr); +#else extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config); +#endif // ENABLE_THUMBNAIL_GENERATOR }; // namespace Slic3r diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 8989487ccc..181d6cb993 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -12,6 +12,7 @@ #include "../PrintConfig.hpp" #include "../Utils.hpp" #include "../I18N.hpp" +#include "../Geometry.hpp" #include "AMF.hpp" @@ -36,7 +37,8 @@ // Added x and y components of rotation // Added x, y and z components of scale // Added x, y and z components of mirror -const unsigned int VERSION_AMF = 2; +// 3 : Meshes saved in their local system; Added volumes' matrices and source data +const unsigned int VERSION_AMF = 3; const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; @@ -560,15 +562,38 @@ void AMFParserContext::endElement(const char * /* name */) stl.stats.number_of_facets = int(m_volume_facets.size() / 3); stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); + + Slic3r::Geometry::Transformation transform; + if (m_version > 2) + transform = m_volume->get_transformation(); + + Transform3d inv_matrix = transform.get_matrix().inverse(); + for (size_t i = 0; i < m_volume_facets.size();) { stl_facet &facet = stl.facet_start[i/3]; - for (unsigned int v = 0; v < 3; ++ v) - memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); + for (unsigned int v = 0; v < 3; ++v) + { + unsigned int tri_id = m_volume_facets[i++] * 3; + Vec3f vertex(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); + if (m_version > 2) + // revert the vertices to the original mesh reference system + vertex = (inv_matrix * vertex.cast()).cast(); + ::memcpy((void*)facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float)); + } } stl_get_size(&stl); mesh.repair(); m_volume->set_mesh(std::move(mesh)); - m_volume->center_geometry_after_creation(); + if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) + { + m_volume->source.object_idx = (int)m_model.objects.size() - 1; + m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; + m_volume->center_geometry_after_creation(); + } + else + // pass false if the mesh offset has been already taken from the data + m_volume->center_geometry_after_creation(m_volume->source.input_file.empty()); + m_volume->calculate_convex_hull(); m_volume_facets.clear(); m_volume = nullptr; @@ -664,6 +689,29 @@ void AMFParserContext::endElement(const char * /* name */) } else if (strcmp(opt_key, "volume_type") == 0) { m_volume->set_type(ModelVolume::type_from_string(m_value[1])); } + else if (strcmp(opt_key, "matrix") == 0) { + Geometry::Transformation transform; + transform.set_from_string(m_value[1]); + m_volume->set_transformation(transform); + } + else if (strcmp(opt_key, "source_file") == 0) { + m_volume->source.input_file = m_value[1]; + } + else if (strcmp(opt_key, "source_object_id") == 0) { + m_volume->source.object_idx = ::atoi(m_value[1].c_str()); + } + else if (strcmp(opt_key, "source_volume_id") == 0) { + m_volume->source.volume_idx = ::atoi(m_value[1].c_str()); + } + else if (strcmp(opt_key, "source_offset_x") == 0) { + m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str()); + } + else if (strcmp(opt_key, "source_offset_y") == 0) { + m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str()); + } + else if (strcmp(opt_key, "source_offset_z") == 0) { + m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str()); + } } } else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { @@ -759,6 +807,15 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) if (result) ctx.endDocument(); + for (ModelObject* o : model->objects) + { + for (ModelVolume* v : o->volumes) + { + if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART)) + v->source.input_file = path; + } + } + return result; } @@ -1057,7 +1114,28 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (volume->is_modifier()) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; - const indexed_triangle_set &its = volume->mesh().its; + stream << " "; + const Transform3d& matrix = volume->get_matrix(); + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + stream << matrix(r, c); + if ((r != 3) || (c != 3)) + stream << " "; + } + } + stream << "\n"; + if (!volume->source.input_file.empty()) + { + stream << " " << xml_escape(volume->source.input_file) << "\n"; + stream << " " << volume->source.object_idx << "\n"; + stream << " " << volume->source.volume_idx << "\n"; + stream << " " << volume->source.mesh_offset(0) << "\n"; + stream << " " << volume->source.mesh_offset(1) << "\n"; + stream << " " << volume->source.mesh_offset(2) << "\n"; + } + const indexed_triangle_set &its = volume->mesh().its; for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index 85ca73551c..aa389781af 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -15,39 +15,41 @@ namespace Slic3r { -bool load_obj(const char *path, Model *model, const char *object_name_in) +bool load_obj(const char *path, TriangleMesh *meshptr) { + if(meshptr == nullptr) return false; + // Parse the OBJ file. ObjParser::ObjData data; if (! ObjParser::objparse(path, data)) { -// die "Failed to parse $file\n" if !-e $path; + // die "Failed to parse $file\n" if !-e $path; return false; } - + // Count the faces and verify, that all faces are triangular. size_t num_faces = 0; - size_t num_quads = 0; + size_t num_quads = 0; for (size_t i = 0; i < data.vertices.size(); ) { size_t j = i; for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ; if (i == j) continue; - size_t face_vertices = j - i; - if (face_vertices != 3 && face_vertices != 4) { + size_t face_vertices = j - i; + if (face_vertices != 3 && face_vertices != 4) { // Non-triangular and non-quad faces are not supported as of now. return false; } - if (face_vertices == 4) - ++ num_quads; - ++ num_faces; + if (face_vertices == 4) + ++ num_quads; + ++ num_faces; i = j + 1; } - + // Convert ObjData into STL. - TriangleMesh mesh; + TriangleMesh &mesh = *meshptr; stl_file &stl = mesh.stl; stl.stats.type = inmemory; - stl.stats.number_of_facets = int(num_faces + num_quads); + stl.stats.number_of_facets = uint32_t(num_faces + num_quads); stl.stats.original_num_facets = int(num_faces + num_quads); // stl_allocate clears all the allocated data to zero, all normals are set to zeros as well. stl_allocate(&stl); @@ -68,14 +70,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) ++ num_normals; } } - if (data.vertices[i].coordIdx != -1) { - // This is a quad. Produce the other triangle. - stl_facet &facet2 = stl.facet_start[i_face++]; + if (data.vertices[i].coordIdx != -1) { + // This is a quad. Produce the other triangle. + stl_facet &facet2 = stl.facet_start[i_face++]; facet2.vertex[0] = facet.vertex[0]; facet2.vertex[1] = facet.vertex[2]; - const ObjParser::ObjVertex &vertex = data.vertices[i++]; - memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); - if (vertex.normalIdx != -1) { + const ObjParser::ObjVertex &vertex = data.vertices[i++]; + memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); + if (vertex.normalIdx != -1) { normal(0) += data.normals[vertex.normalIdx*3]; normal(1) += data.normals[vertex.normalIdx*3+1]; normal(2) += data.normals[vertex.normalIdx*3+2]; @@ -96,25 +98,37 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) if (len > EPSILON) facet.normal = normal / len; } - } + } stl_get_size(&stl); mesh.repair(); if (mesh.facets_count() == 0) { - // die "This STL file couldn't be read because it's empty.\n" + // die "This OBJ file couldn't be read because it's empty.\n" return false; } - - std::string object_name; - if (object_name_in == nullptr) { - const char *last_slash = strrchr(path, DIR_SEPARATOR); - object_name.assign((last_slash == nullptr) ? path : last_slash + 1); - } else - object_name.assign(object_name_in); - - model->add_object(object_name.c_str(), path, std::move(mesh)); + return true; } +bool load_obj(const char *path, Model *model, const char *object_name_in) +{ + TriangleMesh mesh; + + bool ret = load_obj(path, &mesh); + + if (ret) { + std::string object_name; + if (object_name_in == nullptr) { + const char *last_slash = strrchr(path, DIR_SEPARATOR); + object_name.assign((last_slash == nullptr) ? path : last_slash + 1); + } else + object_name.assign(object_name_in); + + model->add_object(object_name.c_str(), path, std::move(mesh)); + } + + return ret; +} + bool store_obj(const char *path, TriangleMesh *mesh) { //FIXME returning false even if write failed. diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index 36aa179512..3eb8b41393 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -5,8 +5,10 @@ namespace Slic3r { class TriangleMesh; class Model; +class ModelObject; // Load an OBJ file into a provided model. +extern bool load_obj(const char *path, TriangleMesh *mesh); extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr); extern bool store_obj(const char *path, TriangleMesh *mesh); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d7f432fde0..63658e817f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,10 @@ #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTower.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR +#include "ShortestPath.hpp" #include "Utils.hpp" #include @@ -17,6 +21,9 @@ #include #include #include +#if ENABLE_THUMBNAIL_GENERATOR +#include +#endif // ENABLE_THUMBNAIL_GENERATOR #include #include @@ -28,6 +35,10 @@ #include +#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE +#include "miniz_extension.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + #if 0 // Enable debugging and asserts, even in the release build. #define DEBUG @@ -116,11 +127,11 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP const Layer* layer1 = object->layers()[i * 2]; const Layer* layer2 = object->layers()[i * 2 + 1]; Polygons polys; - polys.reserve(layer1->slices.expolygons.size() + layer2->slices.expolygons.size()); - for (const ExPolygon &expoly : layer1->slices.expolygons) + polys.reserve(layer1->slices.size() + layer2->slices.size()); + for (const ExPolygon &expoly : layer1->slices) //FIXME no holes? polys.emplace_back(expoly.contour); - for (const ExPolygon &expoly : layer2->slices.expolygons) + for (const ExPolygon &expoly : layer2->slices) //FIXME no holes? polys.emplace_back(expoly.contour); polygons_per_layer[i] = union_(polys); @@ -129,8 +140,8 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP if (object->layers().size() & 1) { const Layer *layer = object->layers().back(); Polygons polys; - polys.reserve(layer->slices.expolygons.size()); - for (const ExPolygon &expoly : layer->slices.expolygons) + polys.reserve(layer->slices.size()); + for (const ExPolygon &expoly : layer->slices) //FIXME no holes? polys.emplace_back(expoly.contour); polygons_per_layer.back() = union_(polys); @@ -506,7 +517,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; - assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size()); + assert(m_layer_idx >= 0); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) @@ -542,7 +553,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec //FIXME should we use the printing extruders instead? double gap_over_supports = object.config().support_material_contact_distance; // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. - assert(gap_over_supports != 0. || object.config().support_material_synchronize_layers); + assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers); if (gap_over_supports != 0.) { gap_over_supports = std::max(0., gap_over_supports); // Not a soluble support, @@ -651,7 +662,11 @@ std::vector>> GCode::collec return layers_to_print; } +#if ENABLE_THUMBNAIL_GENERATOR +void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, const std::vector* thumbnail_data) +#else void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data) +#endif // ENABLE_THUMBNAIL_GENERATOR { PROFILE_CLEAR(); @@ -677,7 +692,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ try { m_placeholder_parser_failed_templates.clear(); +#if ENABLE_THUMBNAIL_GENERATOR + this->_do_export(*print, file, thumbnail_data); +#else this->_do_export(*print, file); +#endif // ENABLE_THUMBNAIL_GENERATOR fflush(file); if (ferror(file)) { fclose(file); @@ -741,7 +760,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); } +#if ENABLE_THUMBNAIL_GENERATOR +void GCode::_do_export(Print& print, FILE* file, const std::vector* thumbnail_data) +#else void GCode::_do_export(Print &print, FILE *file) +#endif // ENABLE_THUMBNAIL_GENERATOR { PROFILE_FUNC(); @@ -777,22 +800,26 @@ void GCode::_do_export(Print &print, FILE *file) { m_silent_time_estimator.reset(); m_silent_time_estimator.set_dialect(print.config().gcode_flavor); - m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[1]); - m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[1]); - m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[1]); - m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[1]); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[1]); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[1]); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[1]); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[1]); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[1]); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[1]); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[1]); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[1]); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[1]); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[1]); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[1]); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[1]); + /* "Stealth mode" values can be just a copy of "normal mode" values + * (when they aren't input for a printer preset). + * Thus, use back value from values, instead of second one, which could be absent + */ + m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back()); + m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back()); + m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back()); + m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back()); + m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back()); + m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back()); + m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back()); + m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back()); + m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back()); + m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back()); + m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back()); + m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back()); + m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back()); + m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back()); + m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back()); + m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back()); if (print.config().single_extruder_multi_material) { // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they // are considered to be active for the single extruder multi-material printers only. @@ -929,6 +956,77 @@ void GCode::_do_export(Print &print, FILE *file) // Write information on the generator. _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); + +#if ENABLE_THUMBNAIL_GENERATOR + // Write thumbnails using base64 encoding + if (thumbnail_data != nullptr) + { + const unsigned int max_row_length = 78; + + for (const ThumbnailData& data : *thumbnail_data) + { + if (data.is_valid()) + { +#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + _write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height); + + std::string encoded = boost::beast::detail::base64_encode((const std::uint8_t*)png_data, png_size); + + unsigned int row_count = 0; + while (encoded.length() > max_row_length) + { + _write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str()); + encoded = encoded.substr(max_row_length); + ++row_count; + } + + if (encoded.length() > 0) + _write_format(file, "; %s\n", encoded.c_str()); + + _write(file, "; thumbnail end\n;\n"); + + mz_free(png_data); + } +#else + _write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height); + + size_t row_size = 4 * data.width; + for (int r = (int)data.height - 1; r >= 0; --r) + { + std::string encoded = boost::beast::detail::base64_encode((const std::uint8_t*)(data.pixels.data() + r * row_size), row_size); + unsigned int row_count = 0; + while (encoded.length() > max_row_length) + { + if (row_count == 0) + _write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str()); + else + _write_format(file, ";>%s\n", encoded.substr(0, max_row_length).c_str()); + + encoded = encoded.substr(max_row_length); + ++row_count; + } + + if (encoded.length() > 0) + { + if (row_count == 0) + _write_format(file, "; %s\n", encoded.c_str()); + else + _write_format(file, ";>%s\n", encoded.c_str()); + } + } + + _write(file, "; thumbnail end\n;\n"); +#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + } + print.throw_if_canceled(); + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR + // Write notes (content of the Print Settings tab -> Notes) { std::list lines; @@ -970,6 +1068,9 @@ void GCode::_do_export(Print &print, FILE *file) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } + // Hold total number of print toolchanges. Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). + int total_toolchanges = std::max(0, print.wipe_tower_data().number_of_toolchanges); + // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); @@ -1032,6 +1133,7 @@ void GCode::_do_export(Print &print, FILE *file) // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); + m_placeholder_parser.set("total_toolchanges", total_toolchanges); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); @@ -1160,7 +1262,7 @@ void GCode::_do_export(Print &print, FILE *file) for (const LayerToPrint <p : layers_to_print) { std::vector lrs; lrs.emplace_back(std::move(ltp)); - this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object.copies().data()); + this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, © - object.copies().data()); print.throw_if_canceled(); } #ifdef HAS_PRESSURE_EQUALIZER @@ -1174,12 +1276,8 @@ void GCode::_do_export(Print &print, FILE *file) } } } else { - // Order objects using a nearest neighbor search. - std::vector object_indices; - Points object_reference_points; - for (PrintObject *object : print.objects()) - object_reference_points.push_back(object->copies().front()); - Slic3r::Geometry::chained_path(object_reference_points, object_indices); + // Order object instances using a nearest neighbor search. + std::vector> print_object_instances_ordering = chain_print_object_instances(print); // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector>> layers_to_print = collect_layers_to_print(print); @@ -1218,7 +1316,7 @@ void GCode::_do_export(Print &print, FILE *file) const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); - this->process_layer(file, print, layer.second, layer_tools, size_t(-1)); + this->process_layer(file, print, layer.second, layer_tools, &print_object_instances_ordering, size_t(-1)); print.throw_if_canceled(); } #ifdef HAS_PRESSURE_EQUALIZER @@ -1286,7 +1384,7 @@ void GCode::_do_export(Print &print, FILE *file) print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true); if (m_silent_time_estimator_enabled) print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true); - + print.m_print_statistics.total_toolchanges = total_toolchanges; std::vector extruders = m_writer.extruders(); if (! extruders.empty()) { std::pair out_filament_used_mm ("; filament used [mm] = ", 0); @@ -1336,6 +1434,8 @@ void GCode::_do_export(Print &print, FILE *file) } _write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight); _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); + if (print.m_print_statistics.total_toolchanges > 0) + _write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); if (m_silent_time_estimator_enabled) _write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str()); @@ -1529,8 +1629,54 @@ inline std::vector& object_islands_by_extruder( return islands; } +std::vector GCode::sort_print_object_instances( + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector> *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx) +{ + std::vector out; + + if (ordering == nullptr) { + // Sequential print, single object is being printed. + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); + } + } else { + // Create mapping from PrintObject* to ObjectByExtruder*. + std::vector> sorted; + sorted.reserve(objects_by_extruder.size()); + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + sorted.emplace_back(print_object, &object_by_extruder); + } + std::sort(sorted.begin(), sorted.end()); + + if (! sorted.empty()) { + const Print &print = *sorted.front().first->print(); + out.reserve(sorted.size()); + for (const std::pair &instance_id : *ordering) { + const PrintObject &print_object = *print.objects()[instance_id.first]; + std::pair key(&print_object, nullptr); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + if (it != sorted.end() && it->first == &print_object) + // ObjectByExtruder for this PrintObject was found. + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance_id.second); + } + } + } + return out; +} + // In sequential mode, process_layer is called once per each object and its copy, -// therefore layers will contain a single entry and single_object_idx will point to the copy of the object. +// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. @@ -1541,14 +1687,16 @@ void GCode::process_layer( // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const LayerTools &layer_tools, + // Pairs of PrintObject index and its instance index. + const std::vector> *ordering, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. - const size_t single_object_idx) + const size_t single_object_instance_idx) { assert(! layers.empty()); // assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. - assert(single_object_idx == size_t(-1) || layers.size() == 1); + assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -1757,16 +1905,24 @@ void GCode::process_layer( // - for each island, we extrude perimeters first, unless user set the infill_first // option // (Still, we have to keep track of regions because we need to apply their config) - size_t n_slices = layer.slices.expolygons.size(); - std::vector layer_surface_bboxes; - layer_surface_bboxes.reserve(n_slices); - for (const ExPolygon &expoly : layer.slices.expolygons) - layer_surface_bboxes.push_back(get_extents(expoly.contour)); + size_t n_slices = layer.slices.size(); + const std::vector &layer_surface_bboxes = layer.slices_bboxes; + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + std::vector slices_test_order; + slices_test_order.reserve(n_slices); + for (size_t i = 0; i < n_slices; ++ i) + slices_test_order.emplace_back(i); + std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) { + const Vec2d s1 = layer_surface_bboxes[i].size().cast(); + const Vec2d s2 = layer_surface_bboxes[j].size().cast(); + return s1.x() * s1.y() < s2.x() * s2.y(); + }); auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { const BoundingBox &bbox = layer_surface_bboxes[i]; return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && point(1) >= bbox.min(1) && point(1) < bbox.max(1) && - layer.slices.expolygons[i].contour.contains(point); + layer.slices[i].contour.contains(point); }; for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) { @@ -1809,16 +1965,19 @@ void GCode::process_layer( extruder, &layer_to_print - layers.data(), layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++i) + for (size_t i = 0; i <= n_slices; ++ i) { + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; if (// fill->first_point does not fit inside any slice - i == n_slices || + last || // fill->first_point fits inside ith slice - point_inside_surface(i, fill->first_point())) { - if (islands[i].by_region.empty()) - islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); - islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); + point_inside_surface(island_idx, fill->first_point())) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); break; } + } } } } @@ -1883,62 +2042,49 @@ void GCode::process_layer( if (objects_by_extruder_it == by_extruder.end()) continue; + std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; - for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) { - const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object == nullptr) - // This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z. - continue; - - m_config.apply(print_object->config(), true); - m_layer = layers[layer_id].layer(); + for (InstanceToPrint &instance_to_print : instances_to_print) { + m_config.apply(instance_to_print.print_object.config(), true); + m_layer = layers[instance_to_print.layer_id].layer(); if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true)); - Points copies; - if (single_object_idx == size_t(-1)) - copies = print_object->copies(); - else - copies.push_back(print_object->copies()[single_object_idx]); - // Sort the copies by the closest point starting with the current print position. - unsigned int copy_id = 0; - for (const Point © : copies) { - if (this->config().gcode_label_objects) - gcode += std::string("; printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; - // When starting a new object, use the external motion planner for the first travel move. - std::pair this_object_copy(print_object, copy); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once = true; - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(copy)); - if (object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layers[layer_id].support_layer; - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. - object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role)); - m_layer = layers[layer_id].layer(); - } - for (ObjectByExtruder::Island &island : object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region; - - if (print.config().infill_first) { - gcode += this->extrude_infill(print, by_region_specific); - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); - } else { - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); - gcode += this->extrude_infill(print,by_region_specific); - } - } - if (this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; - ++ copy_id; + if (this->config().gcode_label_objects) + gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; + // When starting a new object, use the external motion planner for the first travel move. + const Point &offset = instance_to_print.print_object.copies()[instance_to_print.instance_id]; + std::pair this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once = true; + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { + m_layer = layers[instance_to_print.layer_id].support_layer; + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. + instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); + m_layer = layers[instance_to_print.layer_id].layer(); } + for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { + const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region; + + if (print.config().infill_first) { + gcode += this->extrude_infill(print, by_region_specific); + gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + } else { + gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + gcode += this->extrude_infill(print,by_region_specific); + } + } + if (this->config().gcode_label_objects) + gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; } } } @@ -2372,7 +2518,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou static int iRun = 0; SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); if (m_layer->lower_layer != NULL) - svg.draw(m_layer->lower_layer->slices.expolygons); + svg.draw(m_layer->lower_layer->slices); for (size_t i = 0; i < loop.paths.size(); ++ i) svg.draw(loop.paths[i].as_polyline(), "red"); Polylines polylines; @@ -2542,12 +2688,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorconfig()); - ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false); - for (ExtrusionEntity *fill : chained.entities) { + for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) { auto *eec = dynamic_cast(fill); if (eec) { - ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false); - for (ExtrusionEntity *ee : chained2.entities) + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) gcode += this->extrude_entity(*ee, "infill"); } else gcode += this->extrude_entity(*fill, "infill"); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 72813810bd..3183e8883e 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -30,6 +30,9 @@ namespace Slic3r { // Forward declarations. class GCode; class GCodePreviewData; +#if ENABLE_THUMBNAIL_GENERATOR +struct ThumbnailData; +#endif // ENABLE_THUMBNAIL_GENERATOR class AvoidCrossingPerimeters { public: @@ -162,7 +165,11 @@ public: // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). +#if ENABLE_THUMBNAIL_GENERATOR + void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, const std::vector* thumbnail_data = nullptr); +#else void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr); +#endif // ENABLE_THUMBNAIL_GENERATOR // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Vec2d& origin() const { return m_origin; } @@ -190,7 +197,11 @@ public: static void append_full_config(const Print& print, std::string& str); protected: +#if ENABLE_THUMBNAIL_GENERATOR + void _do_export(Print& print, FILE* file, const std::vector* thumbnail_data); +#else void _do_export(Print &print, FILE *file); +#endif //ENABLE_THUMBNAIL_GENERATOR // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint @@ -202,7 +213,7 @@ protected: const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; - static std::vector collect_layers_to_print(const PrintObject &object); + static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); void process_layer( // Write into the output file. @@ -210,7 +221,9 @@ protected: const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, - const LayerTools &layer_tools, + const LayerTools &layer_tools, + // Pairs of PrintObject index and its instance index. + const std::vector> *ordering, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); @@ -258,6 +271,25 @@ protected: std::vector islands; }; + struct InstanceToPrint + { + InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) : + object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {} + + ObjectByExtruder &object_by_extruder; + const size_t layer_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; + }; + + std::vector sort_print_object_instances( + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector> *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx); std::string extrude_perimeters(const Print &print, const std::vector &by_region, std::unique_ptr &lower_layer_edge_grid); std::string extrude_infill(const Print &print, const std::vector &by_region); diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 20f0483b0e..3f0b8735f6 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -20,6 +20,7 @@ static const unsigned int DEFAULT_EXTRUDER_ID = 0; static const unsigned int DEFAULT_COLOR_PRINT_ID = 0; static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f); static const float DEFAULT_START_EXTRUSION = 0.0f; +static const float DEFAULT_FAN_SPEED = 0.0f; namespace Slic3r { @@ -36,21 +37,23 @@ const float GCodeAnalyzer::Default_Height = 0.0f; GCodeAnalyzer::Metadata::Metadata() : extrusion_role(erNone) , extruder_id(DEFAULT_EXTRUDER_ID) - , cp_color_id(DEFAULT_COLOR_PRINT_ID) , mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm) , width(GCodeAnalyzer::Default_Width) , height(GCodeAnalyzer::Default_Height) , feedrate(DEFAULT_FEEDRATE) + , fan_speed(DEFAULT_FAN_SPEED) + , cp_color_id(DEFAULT_COLOR_PRINT_ID) { } -GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id/* = 0*/) +GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id/* = 0*/) : extrusion_role(extrusion_role) , extruder_id(extruder_id) , mm3_per_mm(mm3_per_mm) , width(width) , height(height) , feedrate(feedrate) + , fan_speed(fan_speed) , cp_color_id(cp_color_id) { } @@ -75,15 +78,18 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) if (feedrate != other.feedrate) return true; + if (fan_speed != other.fan_speed) + return true; + if (cp_color_id != other.cp_color_id) return true; return false; } -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id/* = 0*/) +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id/* = 0*/) : type(type) - , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, cp_color_id) + , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, fan_speed, cp_color_id) , start_position(start_position) , end_position(end_position) , delta_extruder(delta_extruder) @@ -133,7 +139,9 @@ void GCodeAnalyzer::reset() _set_feedrate(DEFAULT_FEEDRATE); _set_start_position(DEFAULT_START_POSITION); _set_start_extrusion(DEFAULT_START_EXTRUSION); + _set_fan_speed(DEFAULT_FAN_SPEED); _reset_axes_position(); + _reset_axes_origin(); _reset_cached_position(); m_moves_map.clear(); @@ -259,6 +267,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM83(line); break; } + case 106: // Set fan speed + { + _processM106(line); + break; + } + case 107: // Disable fan + { + _processM107(line); + break; + } case 108: case 135: { @@ -267,6 +285,11 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM108orM135(line); break; } + case 132: // Recall stored home offsets + { + _processM132(line); + break; + } case 401: // Repetier: Store x, y and z position { _processM401(line); @@ -293,31 +316,32 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi m_process_output += line.raw() + "\n"; } -// Returns the new absolute position on the given axis in dependence of the given parameters -float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, bool is_relative, float current_absolute_position) -{ - float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f; - if (lineG1.has(Slic3r::Axis(axis))) - { - float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - return is_relative ? current_absolute_position + ret : ret; - } - else - return current_absolute_position; -} - void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) { + auto axis_absolute_position = [this](GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float + { + float current_absolute_position = _get_axis_position(axis); + float current_origin = _get_axis_origin(axis); + float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f; + + bool is_relative = (_get_global_positioning_type() == Relative); + if (axis == E) + is_relative |= (_get_e_local_positioning_type() == Relative); + + if (lineG1.has(Slic3r::Axis(axis))) + { + float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + return is_relative ? current_absolute_position + ret : ret + current_origin; + } + else + return current_absolute_position; + }; + // updates axes positions from line - EUnits units = _get_units(); float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { - bool is_relative = (_get_global_positioning_type() == Relative); - if (a == E) - is_relative |= (_get_e_local_positioning_type() == Relative); - - new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, _get_axis_position((EAxis)a)); + new_pos[a] = axis_absolute_position((EAxis)a, line); } // updates feedrate from line, if present @@ -407,25 +431,25 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) if (line.has_x()) { - _set_axis_position(X, line.x() * lengthsScaleFactor); + _set_axis_origin(X, _get_axis_position(X) - line.x() * lengthsScaleFactor); anyFound = true; } if (line.has_y()) { - _set_axis_position(Y, line.y() * lengthsScaleFactor); + _set_axis_origin(Y, _get_axis_position(Y) - line.y() * lengthsScaleFactor); anyFound = true; } if (line.has_z()) { - _set_axis_position(Z, line.z() * lengthsScaleFactor); + _set_axis_origin(Z, _get_axis_position(Z) - line.z() * lengthsScaleFactor); anyFound = true; } if (line.has_e()) { - _set_axis_position(E, line.e() * lengthsScaleFactor); + _set_axis_origin(E, _get_axis_position(E) - line.e() * lengthsScaleFactor); anyFound = true; } @@ -433,7 +457,7 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) { for (unsigned char a = X; a < Num_Axis; ++a) { - _set_axis_position((EAxis)a, 0.0f); + _set_axis_origin((EAxis)a, _get_axis_position((EAxis)a)); } } } @@ -448,6 +472,24 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) _set_e_local_positioning_type(Relative); } +void GCodeAnalyzer::_processM106(const GCodeReader::GCodeLine& line) +{ + if (!line.has('P')) + { + // The absence of P means the print cooling fan, so ignore anything else. + float new_fan_speed; + if (line.has_value('S', new_fan_speed)) + _set_fan_speed((100.0f / 256.0f) * new_fan_speed); + else + _set_fan_speed(100.0f); + } +} + +void GCodeAnalyzer::_processM107(const GCodeReader::GCodeLine& line) +{ + _set_fan_speed(0.0f); +} + void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) { // These M-codes are used by MakerWare and Sailfish to change active tool. @@ -467,6 +509,25 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) } } +void GCodeAnalyzer::_processM132(const GCodeReader::GCodeLine& line) +{ + // This command is used by Makerbot to load the current home position from EEPROM + // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md + // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082 + + if (line.has_x()) + _set_axis_origin(X, 0.0f); + + if (line.has_y()) + _set_axis_origin(Y, 0.0f); + + if (line.has_z()) + _set_axis_origin(Z, 0.0f); + + if (line.has_e()) + _set_axis_origin(E, 0.0f); +} + void GCodeAnalyzer::_processM401(const GCodeReader::GCodeLine& line) { if (m_gcode_flavor != gcfRepetier) @@ -726,6 +787,16 @@ float GCodeAnalyzer::_get_feedrate() const return m_state.data.feedrate; } +void GCodeAnalyzer::_set_fan_speed(float fan_speed_percentage) +{ + m_state.data.fan_speed = fan_speed_percentage; +} + +float GCodeAnalyzer::_get_fan_speed() const +{ + return m_state.data.fan_speed; +} + void GCodeAnalyzer::_set_axis_position(EAxis axis, float position) { m_state.position[axis] = position; @@ -736,11 +807,26 @@ float GCodeAnalyzer::_get_axis_position(EAxis axis) const return m_state.position[axis]; } +void GCodeAnalyzer::_set_axis_origin(EAxis axis, float position) +{ + m_state.origin[axis] = position; +} + +float GCodeAnalyzer::_get_axis_origin(EAxis axis) const +{ + return m_state.origin[axis]; +} + void GCodeAnalyzer::_reset_axes_position() { ::memset((void*)m_state.position, 0, Num_Axis * sizeof(float)); } +void GCodeAnalyzer::_reset_axes_origin() +{ + ::memset((void*)m_state.origin, 0, Num_Axis * sizeof(float)); +} + void GCodeAnalyzer::_set_start_position(const Vec3d& position) { m_state.start_position = position; @@ -798,7 +884,7 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) Vec3d start_position = _get_start_position() + extruder_offset; Vec3d end_position = _get_end_position() + extruder_offset; - it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_cp_color_id()); + it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id()); } bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const @@ -821,7 +907,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ } // if layer not found, create and return it - layers.emplace_back(z, ExtrusionPaths()); + layers.emplace_back(z, GCodePreviewData::Extrusion::Paths()); return layers.back(); } @@ -830,13 +916,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ // if the polyline is valid, create the extrusion path from it and store it if (polyline.is_valid()) { - ExtrusionPath path(data.extrusion_role, data.mm3_per_mm, data.width, data.height); + auto& paths = get_layer_at_z(preview_data.extrusion.layers, z).paths; + paths.emplace_back(GCodePreviewData::Extrusion::Path()); + GCodePreviewData::Extrusion::Path &path = paths.back(); path.polyline = polyline; + path.extrusion_role = data.extrusion_role; + path.mm3_per_mm = data.mm3_per_mm; + path.width = data.width; + path.height = data.height; path.feedrate = data.feedrate; path.extruder_id = data.extruder_id; path.cp_color_id = data.cp_color_id; - - get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path); + path.fan_speed = data.fan_speed; } } }; @@ -854,6 +945,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ GCodePreviewData::Range width_range; GCodePreviewData::Range feedrate_range; GCodePreviewData::Range volumetric_rate_range; + GCodePreviewData::Range fan_speed_range; // to avoid to call the callback too often unsigned int cancel_callback_threshold = (unsigned int)std::max((int)extrude_moves->second.size() / 25, 1); @@ -888,6 +980,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ width_range.update_from(move.data.width); feedrate_range.update_from(move.data.feedrate); volumetric_rate_range.update_from(volumetric_rate); + fan_speed_range.update_from(move.data.fan_speed); } else // append end vertex of the move to current polyline @@ -906,6 +999,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ preview_data.ranges.width.update_from(width_range); preview_data.ranges.feedrate.update_from(feedrate_range); preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); + preview_data.ranges.fan_speed.update_from(fan_speed_range); // we need to sort the layers by their z as they can be shuffled in case of sequential prints std::sort(preview_data.extrusion.layers.begin(), preview_data.extrusion.layers.end(), [](const GCodePreviewData::Extrusion::Layer& l1, const GCodePreviewData::Extrusion::Layer& l2)->bool { return l1.z < l2.z; }); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 0372d9da72..df4f6f652c 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -54,10 +54,11 @@ public: float width; // mm float height; // mm float feedrate; // mm/s + float fan_speed; // percentage unsigned int cp_color_id; Metadata(); - Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id = 0); + Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id = 0); bool operator != (const Metadata& other) const; }; @@ -81,7 +82,7 @@ public: Vec3d end_position; float delta_extruder; - GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id = 0); + GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id = 0); GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); }; @@ -100,6 +101,7 @@ private: float cached_position[5]; float start_extrusion; float position[Num_Axis]; + float origin[Num_Axis]; unsigned int cur_cp_color_id = 0; }; @@ -171,9 +173,18 @@ private: // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); + // Set fan speed + void _processM106(const GCodeReader::GCodeLine& line); + + // Disable fan + void _processM107(const GCodeReader::GCodeLine& line); + // Set tool (MakerWare and Sailfish flavor) void _processM108orM135(const GCodeReader::GCodeLine& line); + // Recall stored home offsets + void _processM132(const GCodeReader::GCodeLine& line); + // Repetier: Store x, y and z position void _processM401(const GCodeReader::GCodeLine& line); @@ -233,11 +244,19 @@ private: void _set_feedrate(float feedrate_mm_sec); float _get_feedrate() const; + void _set_fan_speed(float fan_speed_percentage); + float _get_fan_speed() const; + void _set_axis_position(EAxis axis, float position); float _get_axis_position(EAxis axis) const; + void _set_axis_origin(EAxis axis, float position); + float _get_axis_origin(EAxis axis) const; + // Sets axes position to zero void _reset_axes_position(); + // Sets origin position to zero + void _reset_axes_origin(); void _set_start_position(const Vec3d& position); const Vec3d& _get_start_position() const; diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 511089ad0c..b0c35ecc5c 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_CoolingBuffer_hpp_ #define slic3r_CoolingBuffer_hpp_ -#include "libslic3r.h" +#include "../libslic3r.h" #include #include diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 76f21daeb5..53c13a2f23 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -23,7 +23,7 @@ std::vector GCodePreviewData::Color::as_bytes() const return ret; } -GCodePreviewData::Extrusion::Layer::Layer(float z, const ExtrusionPaths& paths) +GCodePreviewData::Extrusion::Layer::Layer(float z, const Paths& paths) : z(z) , paths(paths) { @@ -171,8 +171,8 @@ size_t GCodePreviewData::Extrusion::memory_used() const size_t out = sizeof(*this); out += SLIC3R_STDVEC_MEMSIZE(this->layers, Layer); for (const Layer &layer : this->layers) { - out += SLIC3R_STDVEC_MEMSIZE(layer.paths, ExtrusionPath); - for (const ExtrusionPath &path : layer.paths) + out += SLIC3R_STDVEC_MEMSIZE(layer.paths, Path); + for (const Path &path : layer.paths) out += SLIC3R_STDVEC_MEMSIZE(path.polyline.points, Point); } return out; @@ -241,6 +241,7 @@ void GCodePreviewData::set_default() ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); + ::memcpy((void*)ranges.fan_speed.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); extrusion.set_default(); @@ -287,6 +288,11 @@ GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) con return ranges.feedrate.get_color_at(feedrate); } +GCodePreviewData::Color GCodePreviewData::get_fan_speed_color(float fan_speed) const +{ + return ranges.fan_speed.get_color_at(fan_speed); +} + GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const { return ranges.volumetric_rate.get_color_at(rate); @@ -358,6 +364,8 @@ std::string GCodePreviewData::get_legend_title() const return L("Width (mm)"); case Extrusion::Feedrate: return L("Speed (mm/s)"); + case Extrusion::FanSpeed: + return L("Fan Speed (%)"); case Extrusion::VolumetricRate: return L("Volumetric flow rate (mm³/s)"); case Extrusion::Tool: @@ -421,6 +429,11 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f); break; } + case Extrusion::FanSpeed: + { + Helper::FillListFromRange(items, ranges.fan_speed, 0, 1.0f); + break; + } case Extrusion::VolumetricRate: { Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f); diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index 6490399b42..70b6edffdf 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -52,6 +52,8 @@ public: Range width; // Color mapping by feedrate. Range feedrate; + // Color mapping by fan speed. + Range fan_speed; // Color mapping by volumetric extrusion rate. Range volumetric_rate; }; @@ -74,6 +76,7 @@ public: Height, Width, Feedrate, + FanSpeed, VolumetricRate, Tool, ColorPrint, @@ -84,12 +87,34 @@ public: static const std::string Default_Extrusion_Role_Names[erCount]; static const EViewType Default_View_Type; + class Path + { + public: + Polyline polyline; + ExtrusionRole extrusion_role; + // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. + float mm3_per_mm; + // Width of the extrusion, used for visualization purposes. + float width; + // Height of the extrusion, used for visualization purposes. + float height; + // Feedrate of the extrusion, used for visualization purposes. + float feedrate; + // Id of the extruder, used for visualization purposes. + uint32_t extruder_id; + // Id of the color, used for visualization purposes in the color printing case. + uint32_t cp_color_id; + // Fan speed for the extrusion, used for visualization purposes. + float fan_speed; + }; + using Paths = std::vector; + struct Layer { float z; - ExtrusionPaths paths; + Paths paths; - Layer(float z, const ExtrusionPaths& paths); + Layer(float z, const Paths& paths); }; typedef std::vector LayersList; @@ -205,6 +230,7 @@ public: Color get_height_color(float height) const; Color get_width_color(float width) const; Color get_feedrate_color(float feedrate) const; + Color get_fan_speed_color(float fan_speed) const; Color get_volumetric_rate_color(float rate) const; void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha); diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 07a71a0ea1..d44ef1aad5 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -138,7 +138,7 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ // We need to get position and angle of the wipe tower to transform them to actual position. Transform2d trafo = Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) * - Eigen::Rotation2Dd(print.config().wipe_tower_rotation_angle.value); + Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)); BoundingBoxf bbox; for (const std::vector &tool_changes : print.wipe_tower_data().tool_changes) { diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index 7872b1d3c5..e35ca640c3 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -1,8 +1,8 @@ #ifndef slic3r_SpiralVase_hpp_ #define slic3r_SpiralVase_hpp_ -#include "libslic3r.h" -#include "GCodeReader.hpp" +#include "../libslic3r.h" +#include "../GCodeReader.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/ThumbnailData.cpp b/src/libslic3r/GCode/ThumbnailData.cpp new file mode 100644 index 0000000000..80165916b5 --- /dev/null +++ b/src/libslic3r/GCode/ThumbnailData.cpp @@ -0,0 +1,36 @@ +#include "libslic3r/libslic3r.h" +#include "ThumbnailData.hpp" + +#if ENABLE_THUMBNAIL_GENERATOR + +namespace Slic3r { + +void ThumbnailData::set(unsigned int w, unsigned int h) +{ + if ((w == 0) || (h == 0)) + return; + + if ((width != w) || (height != h)) + { + width = w; + height = h; + // defaults to white texture + pixels = std::vector(width * height * 4, 255); + } +} + +void ThumbnailData::reset() +{ + width = 0; + height = 0; + pixels.clear(); +} + +bool ThumbnailData::is_valid() const +{ + return (width != 0) && (height != 0) && ((unsigned int)pixels.size() == 4 * width * height); +} + +} // namespace Slic3r + +#endif // ENABLE_THUMBNAIL_GENERATOR \ No newline at end of file diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp new file mode 100644 index 0000000000..9823ffd31a --- /dev/null +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -0,0 +1,27 @@ +#ifndef slic3r_ThumbnailData_hpp_ +#define slic3r_ThumbnailData_hpp_ + +#if ENABLE_THUMBNAIL_GENERATOR + +#include + +namespace Slic3r { + +struct ThumbnailData +{ + unsigned int width; + unsigned int height; + std::vector pixels; + + ThumbnailData() { reset(); } + void set(unsigned int w, unsigned int h); + void reset(); + + bool is_valid() const; +}; + +} // namespace Slic3r + +#endif // ENABLE_THUMBNAIL_GENERATOR + +#endif // slic3r_ThumbnailData_hpp_ \ No newline at end of file diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index ea8465f225..73dc91c405 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -331,15 +331,18 @@ public: // Let the firmware back up the active speed override value. WipeTowerWriter& speed_override_backup() - { - m_gcode += "M220 B\n"; + { + // This is only supported by Prusa at this point (https://github.com/prusa3d/PrusaSlicer/issues/3114) + if (m_gcode_flavor == gcfMarlin) + m_gcode += "M220 B\n"; return *this; } // Let the firmware restore the active speed override value. WipeTowerWriter& speed_override_restore() { - m_gcode += "M220 R\n"; + if (m_gcode_flavor == gcfMarlin) + m_gcode += "M220 R\n"; return *this; } @@ -787,8 +790,10 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. box_coordinates box(wipeTower_box); + // the brim shall have 'normal' spacing with no extra void space + float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4); for (size_t i = 0; i < 4; ++ i) { - box.expand(m_perimeter_width - m_layer_height*float(1.-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space + box.expand(spacing); writer.travel (box.ld, 7000) .extrude(box.lu, 2100).extrude(box.ru) .extrude(box.rd ).extrude(box.ld); @@ -800,6 +805,10 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" ";-----------------------------------\n"); + // Save actual brim width to be later passed to the Print object, which will use it + // for skirt calculation and pass it to GLCanvas for precise preview box + m_wipe_tower_brim_width = wipeTower_box.ld.x() - box.ld.x() + spacing/2.f; + m_print_brim = false; // Mark the brim as extruded // Ask our writer about how much material was consumed: diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 5477aa6097..c1ea3f2b56 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -92,6 +92,7 @@ public: void generate(std::vector> &result); float get_depth() const { return m_wipe_tower_depth; } + float get_brim_width() const { return m_wipe_tower_brim_width; } @@ -203,6 +204,7 @@ private: Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower + float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) float m_internal_rotation = 0.f; float m_y_shift = 0.f; // y shift passed to writer diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index f64605a9c5..24f9792675 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -29,6 +29,8 @@ public: float value(Axis axis) const { return m_axis[axis]; } bool has(char axis) const; bool has_value(char axis, float &value) const; + float new_X(const GCodeReader &reader) const { return this->has(X) ? this->x() : reader.x(); } + float new_Y(const GCodeReader &reader) const { return this->has(Y) ? this->y() : reader.y(); } float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); } diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 4693ba9e62..c624c0fce8 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -318,12 +318,15 @@ namespace Slic3r { assert((g1_line_id >= (int)data->g1_line_ids.size()) || (data->g1_line_ids[g1_line_id].first >= g1_lines_count)); const Block* block = nullptr; - const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id]; - if ((g1_line_id < (int)data->g1_line_ids.size()) && (map_item.first == g1_lines_count)) + if (g1_line_id < (int)data->g1_line_ids.size()) { - if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size())) - block = &data->blocks[map_item.second]; - ++g1_line_id; + const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id]; + if (map_item.first == g1_lines_count) + { + if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size())) + block = &data->blocks[map_item.second]; + ++g1_line_id; + } } if ((block != nullptr) && (block->elapsed_time != -1.0f)) @@ -412,6 +415,11 @@ namespace Slic3r { m_state.axis[axis].position = position; } + void GCodeTimeEstimator::set_axis_origin(EAxis axis, float position) + { + m_state.axis[axis].origin = position; + } + void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) { m_state.axis[axis].max_feedrate = feedrate_mm_sec; @@ -432,6 +440,11 @@ namespace Slic3r { return m_state.axis[axis].position; } + float GCodeTimeEstimator::get_axis_origin(EAxis axis) const + { + return m_state.axis[axis].origin; + } + float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const { return m_state.axis[axis].max_feedrate; @@ -758,6 +771,10 @@ namespace Slic3r { set_axis_position(X, 0.0f); set_axis_position(Y, 0.0f); set_axis_position(Z, 0.0f); + set_axis_origin(X, 0.0f); + set_axis_origin(Y, 0.0f); + set_axis_origin(Z, 0.0f); + if (get_e_local_positioning_type() == Absolute) set_axis_position(E, 0.0f); @@ -954,34 +971,35 @@ namespace Slic3r { } } - // Returns the new absolute position on the given axis in dependence of the given parameters - float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, bool is_relative, float current_absolute_position) - { - float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f; - if (lineG1.has(Slic3r::Axis(axis))) - { - float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - return is_relative ? current_absolute_position + ret : ret; - } - else - return current_absolute_position; - } - void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line) { + auto axis_absolute_position = [this](GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float + { + float current_absolute_position = get_axis_position(axis); + float current_origin = get_axis_origin(axis); + float lengthsScaleFactor = (get_units() == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f; + + bool is_relative = (get_global_positioning_type() == Relative); + if (axis == E) + is_relative |= (get_e_local_positioning_type() == Relative); + + if (lineG1.has(Slic3r::Axis(axis))) + { + float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + return is_relative ? current_absolute_position + ret : ret + current_origin; + } + else + return current_absolute_position; + }; + PROFILE_FUNC(); increment_g1_line_id(); // updates axes positions from line - EUnits units = get_units(); float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { - bool is_relative = (get_global_positioning_type() == Relative); - if (a == E) - is_relative |= (get_e_local_positioning_type() == Relative); - - new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, get_axis_position((EAxis)a)); + new_pos[a] = axis_absolute_position((EAxis)a, line); } // updates feedrate from line, if present @@ -1225,25 +1243,25 @@ namespace Slic3r { if (line.has_x()) { - set_axis_position(X, line.x() * lengthsScaleFactor); + set_axis_origin(X, get_axis_position(X) - line.x() * lengthsScaleFactor); anyFound = true; } if (line.has_y()) { - set_axis_position(Y, line.y() * lengthsScaleFactor); + set_axis_origin(Y, get_axis_position(Y) - line.y() * lengthsScaleFactor); anyFound = true; } if (line.has_z()) { - set_axis_position(Z, line.z() * lengthsScaleFactor); + set_axis_origin(Z, get_axis_position(Z) - line.z() * lengthsScaleFactor); anyFound = true; } if (line.has_e()) { - set_axis_position(E, line.e() * lengthsScaleFactor); + set_axis_origin(E, get_axis_position(E) - line.e() * lengthsScaleFactor); anyFound = true; } else @@ -1253,7 +1271,7 @@ namespace Slic3r { { for (unsigned char a = X; a < Num_Axis; ++a) { - set_axis_position((EAxis)a, 0.0f); + set_axis_origin((EAxis)a, get_axis_position((EAxis)a)); } } } diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index d9f3bc2113..0219c87d1e 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -55,6 +55,7 @@ namespace Slic3r { struct Axis { float position; // mm + float origin; // mm float max_feedrate; // mm/s float max_acceleration; // mm/s^2 float max_jerk; // mm/s @@ -282,6 +283,8 @@ namespace Slic3r { // Set current position on the given axis with the given value void set_axis_position(EAxis axis, float position); + // Set current origin on the given axis with the given value + void set_axis_origin(EAxis axis, float position); void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec); void set_axis_max_acceleration(EAxis axis, float acceleration); @@ -289,6 +292,8 @@ namespace Slic3r { // Returns current position on the given axis float get_axis_position(EAxis axis) const; + // Returns current origin on the given axis + float get_axis_origin(EAxis axis) const; float get_axis_max_feedrate(EAxis axis) const; float get_axis_max_acceleration(EAxis axis) const; diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 51fca58f64..364ba12ae1 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -269,7 +269,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s assert(F > 0.); assert(F < 100000.); std::ostringstream gcode; - gcode << "G1 F" << F; + gcode << "G1 F" << XYZF_NUM(F); COMMENT(comment); gcode << cooling_marker; gcode << "\n"; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index cc8a86a96c..46d7ef1543 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -3,18 +3,22 @@ #include "ClipperUtils.hpp" #include "ExPolygon.hpp" #include "Line.hpp" -#include "PolylineCollection.hpp" #include "clipper.hpp" #include #include #include #include #include +#include #include #include #include #include +#include +#include +#include + #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif @@ -309,49 +313,7 @@ convex_hull(const Polygons &polygons) return convex_hull(std::move(pp)); } -/* accepts an arrayref of points and returns a list of indices - according to a nearest-neighbor walk */ -void -chained_path(const Points &points, std::vector &retval, Point start_near) -{ - PointConstPtrs my_points; - std::map indices; - my_points.reserve(points.size()); - for (Points::const_iterator it = points.begin(); it != points.end(); ++it) { - my_points.push_back(&*it); - indices[&*it] = it - points.begin(); - } - - retval.reserve(points.size()); - while (!my_points.empty()) { - Points::size_type idx = start_near.nearest_point_index(my_points); - start_near = *my_points[idx]; - retval.push_back(indices[ my_points[idx] ]); - my_points.erase(my_points.begin() + idx); - } -} - -void -chained_path(const Points &points, std::vector &retval) -{ - if (points.empty()) return; // can't call front() on empty vector - chained_path(points, retval, points.front()); -} - -/* retval and items must be different containers */ -template -void -chained_path_items(Points &points, T &items, T &retval) -{ - std::vector indices; - chained_path(points, indices); - for (std::vector::const_iterator it = indices.begin(); it != indices.end(); ++it) - retval.push_back(items[*it]); -} -template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval); - -bool -directions_parallel(double angle1, double angle2, double max_diff) +bool directions_parallel(double angle1, double angle2, double max_diff) { double diff = fabs(angle1 - angle2); max_diff += EPSILON; @@ -359,8 +321,7 @@ directions_parallel(double angle1, double angle2, double max_diff) } template -bool -contains(const std::vector &vector, const Point &point) +bool contains(const std::vector &vector, const Point &point) { for (typename std::vector::const_iterator it = vector.begin(); it != vector.end(); ++it) { if (it->contains(point)) return true; @@ -369,16 +330,101 @@ contains(const std::vector &vector, const Point &point) } template bool contains(const ExPolygons &vector, const Point &point); -double -rad2deg_dir(double angle) +double rad2deg_dir(double angle) { angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); if (angle < 0) angle += PI; return rad2deg(angle); } -void -simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) +Point circle_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles) +{ + Vec2ds tmp; + tmp.reserve(std::distance(input_begin, input_end)); + std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } ); + Vec2d center = circle_taubin_newton(tmp.cbegin(), tmp.end(), cycles); + return Point::new_scale(center.x(), center.y()); +} + +/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126 +/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end +/// lie on. +Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles) +{ + // calculate the centroid of the data set + const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0)); + const size_t n = std::distance(input_begin, input_end); + const double n_flt = static_cast(n); + const Vec2d centroid { sum / n_flt }; + + // Compute the normalized moments of the data set. + double Mxx = 0, Myy = 0, Mxy = 0, Mxz = 0, Myz = 0, Mzz = 0; + for (auto it = input_begin; it < input_end; ++it) { + // center/normalize the data. + double Xi {it->x() - centroid.x()}; + double Yi {it->y() - centroid.y()}; + double Zi {Xi*Xi + Yi*Yi}; + Mxy += (Xi*Yi); + Mxx += (Xi*Xi); + Myy += (Yi*Yi); + Mxz += (Xi*Zi); + Myz += (Yi*Zi); + Mzz += (Zi*Zi); + } + + // divide by number of points to get the moments + Mxx /= n_flt; + Myy /= n_flt; + Mxy /= n_flt; + Mxz /= n_flt; + Myz /= n_flt; + Mzz /= n_flt; + + // Compute the coefficients of the characteristic polynomial for the circle + // eq 5.60 + const double Mz {Mxx + Myy}; // xx + yy = z + const double Cov_xy {Mxx*Myy - Mxy*Mxy}; // this shows up a couple times so cache it here. + const double C3 {4.0*Mz}; + const double C2 {-3.0*(Mz*Mz) - Mzz}; + const double C1 {Mz*(Mzz - (Mz*Mz)) + 4.0*Mz*Cov_xy - (Mxz*Mxz) - (Myz*Myz)}; + const double C0 {(Mxz*Mxz)*Myy + (Myz*Myz)*Mxx - 2.0*Mxz*Myz*Mxy - Cov_xy*(Mzz - (Mz*Mz))}; + + const double C22 = {C2 + C2}; + const double C33 = {C3 + C3 + C3}; + + // solve the characteristic polynomial with Newton's method. + double xnew = 0.0; + double ynew = 1e20; + + for (size_t i = 0; i < cycles; ++i) { + const double yold {ynew}; + ynew = C0 + xnew * (C1 + xnew*(C2 + xnew * C3)); + if (std::abs(ynew) > std::abs(yold)) { + BOOST_LOG_TRIVIAL(error) << "Geometry: Fit is going in the wrong direction.\n"; + return Vec2d(std::nan(""), std::nan("")); + } + const double Dy {C1 + xnew*(C22 + xnew*C33)}; + + const double xold {xnew}; + xnew = xold - (ynew / Dy); + + if (std::abs((xnew-xold) / xnew) < 1e-12) i = cycles; // converged, we're done here + + if (xnew < 0) { + // reset, we went negative + xnew = 0.0; + } + } + + // compute the determinant and the circle's parameters now that we've solved. + double DET = xnew*xnew - xnew*Mz + Cov_xy; + + Vec2d center(Mxz * (Myy - xnew) - Myz * Mxy, Myz * (Mxx - xnew) - Mxz*Mxy); + center /= (DET * 2.); + return center + centroid; +} + +void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { Polygons pp; for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { @@ -391,8 +437,7 @@ simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) *retval = Slic3r::simplify_polygons(pp); } -double -linint(double value, double oldmin, double oldmax, double newmin, double newmax) +double linint(double value, double oldmin, double oldmax, double newmin, double newmax) { return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } @@ -618,7 +663,6 @@ namespace Voronoi { namespace Internal { typedef boost::polygon::point_data point_type; typedef boost::polygon::segment_data segment_type; typedef boost::polygon::rectangle_data rect_type; -// typedef voronoi_builder VB; typedef boost::polygon::voronoi_diagram VD; typedef VD::cell_type cell_type; typedef VD::cell_type::source_index_type source_index_type; @@ -665,15 +709,15 @@ namespace Voronoi { namespace Internal { if (cell1.contains_point() && cell2.contains_point()) { point_type p1 = retrieve_point(segments, cell1); point_type p2 = retrieve_point(segments, cell2); - origin.x((p1(0) + p2(0)) * 0.5); - origin.y((p1(1) + p2(1)) * 0.5); - direction.x(p1(1) - p2(1)); - direction.y(p2(0) - p1(0)); + origin.x((p1.x() + p2.x()) * 0.5); + origin.y((p1.y() + p2.y()) * 0.5); + direction.x(p1.y() - p2.y()); + direction.y(p2.x() - p1.x()); } else { origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1); segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; - coordinate_type dx = high(segment)(0) - low(segment)(0); - coordinate_type dy = high(segment)(1) - low(segment)(1); + coordinate_type dx = high(segment).x() - low(segment).x(); + coordinate_type dy = high(segment).y() - low(segment).y(); if ((low(segment) == origin) ^ cell1.contains_point()) { direction.x(dy); direction.y(-dx); @@ -682,19 +726,19 @@ namespace Voronoi { namespace Internal { direction.y(dx); } } - coordinate_type koef = bbox_max_size / (std::max)(fabs(direction(0)), fabs(direction(1))); + coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); if (edge.vertex0() == NULL) { clipped_edge->push_back(point_type( - origin(0) - direction(0) * koef, - origin(1) - direction(1) * koef)); + origin.x() - direction.x() * koef, + origin.y() - direction.y() * koef)); } else { clipped_edge->push_back( point_type(edge.vertex0()->x(), edge.vertex0()->y())); } if (edge.vertex1() == NULL) { clipped_edge->push_back(point_type( - origin(0) + direction(0) * koef, - origin(1) + direction(1) * koef)); + origin.x() + direction.x() * koef, + origin.y() + direction.y() * koef)); } else { clipped_edge->push_back( point_type(edge.vertex1()->x(), edge.vertex1()->y())); @@ -714,7 +758,7 @@ namespace Voronoi { namespace Internal { } /* namespace Internal */ } // namespace Voronoi -static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_diagram &vd, const ThickPolylines *polylines, const char *path) +static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ boost::polygon::voronoi_diagram &vd, const ThickPolylines *polylines, const char *path) { const double scale = 0.2; const std::string inputSegmentPointColor = "lightseagreen"; @@ -758,7 +802,7 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1))))); // Color exterior edges. - for (voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) if (!it->is_finite()) Voronoi::Internal::color_exterior(&(*it)); @@ -773,11 +817,11 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d #if 1 // Draw voronoi vertices. - for (voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) + for (boost::polygon::voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) - svg.draw(Point(coord_t((*it)(0)), coord_t((*it)(1))), voronoiPointColor, voronoiPointRadius); + svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); - for (voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { if (primaryEdgesOnly && !it->is_primary()) continue; if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) @@ -800,7 +844,7 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d color = voronoiLineColorSecondary; } for (std::size_t i = 0; i + 1 < samples.size(); ++i) - svg.draw(Line(Point(coord_t(samples[i](0)), coord_t(samples[i](1))), Point(coord_t(samples[i+1](0)), coord_t(samples[i+1](1)))), color, voronoiLineWidth); + svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); } #endif @@ -1376,6 +1420,32 @@ void Transformation::set_from_transform(const Transform3d& transform) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } +void Transformation::set_from_string(const std::string& transform_str) +{ + Transform3d transform = Transform3d::Identity(); + + if (!transform_str.empty()) + { + std::vector mat_elements_str; + boost::split(mat_elements_str, transform_str, boost::is_any_of(" "), boost::token_compress_on); + + unsigned int size = (unsigned int)mat_elements_str.size(); + if (size == 16) + { + unsigned int i = 0; + for (unsigned int r = 0; r < 4; ++r) + { + for (unsigned int c = 0; c < 4; ++c) + { + transform(r, c) = ::atof(mat_elements_str[i++].c_str()); + } + } + } + } + + set_from_transform(transform); +} + void Transformation::reset() { m_offset = Vec3d::Zero(); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index eec2673227..d996658f27 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -11,8 +11,11 @@ #include #include "boost/polygon/voronoi.hpp" -using boost::polygon::voronoi_builder; -using boost::polygon::voronoi_diagram; + +namespace ClipperLib { +class PolyNode; +using PolyNodes = std::vector; +} namespace Slic3r { namespace Geometry { @@ -138,9 +141,6 @@ Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); -void chained_path(const Points &points, std::vector &retval, Point start_near); -void chained_path(const Points &points, std::vector &retval); -template void chained_path_items(Points &points, T &items, T &retval); bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } @@ -160,6 +160,15 @@ template T angle_to_0_2PI(T angle) return angle; } + +/// Find the center of the circle corresponding to the vector of Points as an arc. +Point circle_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20); +inline Point circle_taubin_newton(const Points& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); } + +/// Find the center of the circle corresponding to the vector of Pointfs as an arc. +Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20); +inline Vec2d circle_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); } + void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); double linint(double value, double oldmin, double oldmax, double newmin, double newmax); @@ -181,7 +190,7 @@ class MedialAxis { void build(Polylines* polylines); private: - class VD : public voronoi_diagram { + class VD : public boost::polygon::voronoi_diagram { public: typedef double coord_type; typedef boost::polygon::point_data point_type; @@ -278,6 +287,7 @@ public: void set_mirror(Axis axis, double mirror); void set_from_transform(const Transform3d& transform); + void set_from_string(const std::string& transform_str); void reset(); diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp new file mode 100644 index 0000000000..3cccfdafac --- /dev/null +++ b/src/libslic3r/KDTreeIndirect.hpp @@ -0,0 +1,233 @@ +// KD tree built upon external data set, referencing the external data by integer indices. + +#ifndef slic3r_KDTreeIndirect_hpp_ +#define slic3r_KDTreeIndirect_hpp_ + +#include +#include +#include + +#include "Utils.hpp" // for next_highest_power_of_2() + +namespace Slic3r { + +// KD tree for N-dimensional closest point search. +template +class KDTreeIndirect +{ +public: + static constexpr size_t NumDimensions = ANumDimensions; + using CoordinateFn = ACoordinateFn; + using CoordType = ACoordType; + // Following could be static constexpr size_t, but that would not link in C++11 + enum : size_t { + npos = size_t(-1) + }; + + KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {} + KDTreeIndirect(CoordinateFn coordinate, std::vector indices) : coordinate(coordinate) { this->build(std::move(indices)); } + KDTreeIndirect(CoordinateFn coordinate, std::vector &&indices) : coordinate(coordinate) { this->build(std::move(indices)); } + KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); } + KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {} + KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; } + void clear() { m_nodes.clear(); } + + void build(size_t num_indices) + { + std::vector indices; + indices.reserve(num_indices); + for (size_t i = 0; i < num_indices; ++ i) + indices.emplace_back(i); + this->build(std::move(indices)); + } + + void build(std::vector &&indices) + { + if (indices.empty()) + clear(); + else { + // Allocate a next highest power of 2 nodes, because the incomplete binary tree will not have the leaves filled strictly from the left. + m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos); + build_recursive(indices, 0, 0, 0, (int)(indices.size() - 1)); + } + indices.clear(); + } + + enum class VisitorReturnMask : unsigned int + { + CONTINUE_LEFT = 1, + CONTINUE_RIGHT = 2, + STOP = 4, + }; + template + unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const + { + CoordType dist = point_coord - this->coordinate(idx, dimension); + return (dist * dist < search_radius + CoordType(EPSILON)) ? + // The plane intersects a hypersphere centered at point_coord of search_radius. + ((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) : + // The plane does not intersect the hypersphere. + (dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT); + } + + // Visitor is supposed to return a bit mask of VisitorReturnMask. + template + void visit(Visitor &visitor) const + { + visit_recursive(0, 0, visitor); + } + + CoordinateFn coordinate; + +private: + // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. + void build_recursive(std::vector &input, size_t node, int dimension, int left, int right) + { + if (left > right) + return; + + assert(node < m_nodes.size()); + + if (left == right) { + // Insert a node into the balanced tree. + m_nodes[node] = input[left]; + return; + } + + // Partition the input sequence to two equal halves. + int center = (left + right) >> 1; + partition_input(input, dimension, left, right, center); + // Insert a node into the tree. + m_nodes[node] = input[center]; + // Partition the left and right subtrees. + size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension; + build_recursive(input, (node << 1) + 1, next_dimension, left, center - 1); + build_recursive(input, (node << 1) + 2, next_dimension, center + 1, right); + } + + // Partition the input m_nodes at k using QuickSelect method. + // https://en.wikipedia.org/wiki/Quickselect + void partition_input(std::vector &input, int dimension, int left, int right, int k) const + { + while (left < right) { + // Guess the k'th element. + // Pick the pivot as a median of first, center and last value. + // Sort first, center and last values. + int center = (left + right) >> 1; + auto left_value = this->coordinate(input[left], dimension); + auto center_value = this->coordinate(input[center], dimension); + auto right_value = this->coordinate(input[right], dimension); + if (center_value < left_value) { + std::swap(input[left], input[center]); + std::swap(left_value, center_value); + } + if (right_value < left_value) { + std::swap(input[left], input[right]); + std::swap(left_value, right_value); + } + if (right_value < center_value) { + std::swap(input[center], input[right]); + // No need to do that, result is not used. + // std::swap(center_value, right_value); + } + // Only two or three values are left and those are sorted already. + if (left + 3 > right) + break; + // left and right items are already at their correct positions. + // input[left].point[dimension] <= input[center].point[dimension] <= input[right].point[dimension] + // Move the pivot to the (right - 1) position. + std::swap(input[center], input[right - 1]); + // Pivot value. + double pivot = this->coordinate(input[right - 1], dimension); + // Partition the set based on the pivot. + int i = left; + int j = right - 1; + for (;;) { + // Skip left points that are already at correct positions. + // Search will certainly stop at position (right - 1), which stores the pivot. + while (this->coordinate(input[++ i], dimension) < pivot) ; + // Skip right points that are already at correct positions. + while (this->coordinate(input[-- j], dimension) > pivot && i < j) ; + if (i >= j) + break; + std::swap(input[i], input[j]); + } + // Restore pivot to the center of the sequence. + std::swap(input[i], input[right]); + // Which side the kth element is in? + if (k < i) + right = i - 1; + else if (k == i) + // Sequence is partitioned, kth element is at its place. + break; + else + left = i + 1; + } + } + + template + void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const + { + assert(! m_nodes.empty()); + if (node >= m_nodes.size() || m_nodes[node] == npos) + return; + + // Left / right child node index. + size_t left = (node << 1) + 1; + size_t right = left + 1; + unsigned int mask = visitor(m_nodes[node], dimension); + if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) { + size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension; + if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT) + visit_recursive(left, next_dimension, visitor); + if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT) + visit_recursive(right, next_dimension, visitor); + } + } + + std::vector m_nodes; +}; + +// Find a closest point using Euclidian metrics. +// Returns npos if not found. +template +size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) +{ + struct Visitor { + using CoordType = typename KDTreeIndirectType::CoordType; + const KDTreeIndirectType &kdtree; + const PointType &point; + const FilterFn filter; + size_t min_idx = KDTreeIndirectType::npos; + CoordType min_dist = std::numeric_limits::max(); + + Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {} + unsigned int operator()(size_t idx, size_t dimension) { + if (this->filter(idx)) { + auto dist = CoordType(0); + for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) { + CoordType d = point[i] - kdtree.coordinate(idx, i); + dist += d * d; + } + if (dist < min_dist) { + min_dist = dist; + min_idx = idx; + } + } + return kdtree.descent_mask(point[dimension], min_dist, idx, dimension); + } + } visitor(kdtree, point, filter); + + kdtree.visit(visitor); + return visitor.min_idx; +} + +template +size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point) +{ + return find_closest_point(kdtree, point, [](size_t) { return true; }); +} + +} // namespace Slic3r + +#endif /* slic3r_KDTreeIndirect_hpp_ */ diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 4ac64b7775..53a7f2fc45 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -1,8 +1,8 @@ #include "Layer.hpp" #include "ClipperUtils.hpp" -#include "Geometry.hpp" #include "Print.hpp" #include "Fill/Fill.hpp" +#include "ShortestPath.hpp" #include "SVG.hpp" #include @@ -47,8 +47,8 @@ void Layer::make_slices() slices = union_ex(slices_p); } - this->slices.expolygons.clear(); - this->slices.expolygons.reserve(slices.size()); + this->slices.clear(); + this->slices.reserve(slices.size()); // prepare ordering points Points ordering_points; @@ -57,12 +57,11 @@ void Layer::make_slices() ordering_points.push_back(ex.contour.first_point()); // sort slices - std::vector order; - Slic3r::Geometry::chained_path(ordering_points, order); + std::vector order = chain_points(ordering_points); // populate slices vector for (size_t i : order) - this->slices.expolygons.push_back(std::move(slices[i])); + this->slices.push_back(std::move(slices[i])); } // Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill. @@ -71,7 +70,7 @@ void Layer::merge_slices() if (m_regions.size() == 1) { // Optimization, also more robust. Don't merge classified pieces of layerm->slices, // but use the non-split islands of a layer. For a single region print, these shall be equal. - m_regions.front()->slices.set(this->slices.expolygons, stInternal); + m_regions.front()->slices.set(this->slices, stInternal); } else { for (LayerRegion *layerm : m_regions) // without safety offset, artifacts are generated (GH #2494) @@ -89,8 +88,12 @@ ExPolygons Layer::merged(float offset_scaled) const offset_scaled2 = float(- EPSILON); } Polygons polygons; - for (LayerRegion *layerm : m_regions) - append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + for (LayerRegion *layerm : m_regions) { + const PrintRegionConfig &config = layerm->region()->config(); + // Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty. + if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0) + append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + } ExPolygons out = union_ex(polygons); if (offset_scaled2 != 0.f) out = offset_ex(out, offset_scaled2); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 555017207a..9a4297ce55 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -6,8 +6,6 @@ #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" #include "ExPolygonCollection.hpp" -#include "PolylineCollection.hpp" - namespace Slic3r { @@ -48,7 +46,7 @@ public: Polygons bridged; // collection of polylines representing the unsupported bridge edges - PolylineCollection unsupported_bridge_edges; + Polylines unsupported_bridge_edges; // ordered collection of extrusion paths/loops to build all perimeters // (this collection contains only ExtrusionEntityCollection objects) @@ -112,7 +110,8 @@ public: // also known as 'islands' (all regions and surface types are merged here) // The slices are chained by the shortest traverse distance and this traversal // order will be recovered by the G-code generator. - ExPolygonCollection slices; + ExPolygons slices; + std::vector slices_bboxes; size_t region_count() const { return m_regions.size(); } const LayerRegion* get_region(int idx) const { return m_regions.at(idx); } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index bfe96d311c..35acaf9983 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -70,7 +70,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec fill_surfaces ); - if (this->layer()->lower_layer != NULL) + if (this->layer()->lower_layer != nullptr) // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->slices; @@ -88,7 +88,6 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { - const Surfaces &surfaces = this->fill_surfaces.surfaces; const bool has_infill = this->region()->config().fill_density.value > 0.; const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); @@ -130,7 +129,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges.emplace_back(surface); } if (surface.is_internal()) { - assert(surface.surface_type == stInternal); + assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); if (! has_infill && lower_layer != nullptr) polygons_append(voids, surface.expolygon); internal.emplace_back(std::move(surface)); @@ -140,7 +139,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // Remove voids from fill_boundaries, that are not supported by the layer below. if (lower_layer_covered == nullptr) { lower_layer_covered = &lower_layer_covered_tmp; - lower_layer_covered_tmp = to_polygons(lower_layer->slices.expolygons); + lower_layer_covered_tmp = to_polygons(lower_layer->slices); } if (! lower_layer_covered->empty()) voids = diff(voids, *lower_layer_covered); @@ -272,7 +271,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config().support_material) { polygons_append(this->bridged, bd.coverage()); - this->unsupported_bridge_edges.append(bd.unsupported_edges()); + append(this->unsupported_bridge_edges, bd.unsupported_edges()); } } else if (custom_angle > 0) { // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index baa04795ad..e5f7b8fa91 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -86,10 +86,7 @@ bool Line::intersection(const Line &l2, Point *intersection) const const Line &l1 = *this; const Vec2d v1 = (l1.b - l1.a).cast(); const Vec2d v2 = (l2.b - l2.a).cast(); - const Vec2d v12 = (l1.a - l2.a).cast(); double denom = cross2(v1, v2); - double nume_a = cross2(v2, v12); - double nume_b = cross2(v1, v12); if (fabs(denom) < EPSILON) #if 0 // Lines are collinear. Return true if they are coincident (overlappign). @@ -97,6 +94,9 @@ bool Line::intersection(const Line &l2, Point *intersection) const #else return false; #endif + const Vec2d v12 = (l1.a - l2.a).cast(); + double nume_a = cross2(v2, v12); + double nume_b = cross2(v1, v12); double t1 = nume_a / denom; double t2 = nume_b / denom; if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) { diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 212752883c..63ff6fb09d 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -252,22 +252,15 @@ template struct remove_cvref template using remove_cvref_t = typename remove_cvref::type; -template class C, class T> -class Container : public C> -{ -public: - explicit Container(size_t count, T &&initval) - : C>(count, initval) - {} -}; - template using DefaultContainer = std::vector; /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html -template class C = DefaultContainer> -inline C> linspace(const T &start, const T &stop, const I &n) +template class Container = DefaultContainer> +inline Container> linspace(const T &start, + const T &stop, + const I &n) { - Container vals(n, T()); + Container> vals(n, T()); T stride = (stop - start) / n; size_t i = 0; @@ -282,10 +275,13 @@ inline C> linspace(const T &start, const T &stop, const I &n) /// in the closest multiple of 'stride' less than or equal to 'end' and /// leaving 'stride' space between each value. /// Very similar to Matlab [start:stride:end] notation. -template class C = DefaultContainer> -inline C> grid(const T &start, const T &stop, const T &stride) +template class Container = DefaultContainer> +inline Container> grid(const T &start, + const T &stop, + const T &stride) { - Container vals(size_t(std::ceil((stop - start) / stride)), T()); + Container> + vals(size_t(std::ceil((stop - start) / stride)), T()); int i = 0; std::generate(vals.begin(), vals.end(), [&i, start, stride] { @@ -387,10 +383,12 @@ unscaled(const Eigen::Matrix &v) noexcept return v.template cast() * SCALING_FACTOR; } -template inline std::vector reserve_vector(size_t capacity) +template // Arbitrary allocator can be used +inline IntegerOnly> reserve_vector(I capacity) { - std::vector ret; - ret.reserve(capacity); + std::vector ret; + if (capacity > I(0)) ret.reserve(size_t(capacity)); + return ret; } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 1e06f0703e..061c5bd50a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -141,12 +141,12 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig for (ModelObject *o : model.objects) { - if (boost::algorithm::iends_with(input_file, ".zip.amf")) - { - // we remove the .zip part of the extension to avoid it be added to filenames when exporting - o->input_file = boost::ireplace_last_copy(input_file, ".zip.", "."); - } - else +// if (boost::algorithm::iends_with(input_file, ".zip.amf")) +// { +// // we remove the .zip part of the extension to avoid it be added to filenames when exporting +// o->input_file = boost::ireplace_last_copy(input_file, ".zip.", "."); +// } +// else o->input_file = input_file; } @@ -170,6 +170,9 @@ ModelObject* Model::add_object(const char *name, const char *path, const Triangl new_object->input_file = path; ModelVolume *new_volume = new_object->add_volume(mesh); new_volume->name = name; + new_volume->source.input_file = path; + new_volume->source.object_idx = (int)this->objects.size() - 1; + new_volume->source.volume_idx = (int)new_object->volumes.size() - 1; new_object->invalidate_bounding_box(); return new_object; } @@ -182,6 +185,9 @@ ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh new_object->input_file = path; ModelVolume *new_volume = new_object->add_volume(std::move(mesh)); new_volume->name = name; + new_volume->source.input_file = path; + new_volume->source.object_idx = (int)this->objects.size() - 1; + new_volume->source.volume_idx = (int)new_object->volumes.size() - 1; new_object->invalidate_bounding_box(); return new_object; } @@ -1543,7 +1549,7 @@ bool ModelVolume::is_splittable() const return m_is_splittable == 1; } -void ModelVolume::center_geometry_after_creation() +void ModelVolume::center_geometry_after_creation(bool update_source_offset) { Vec3d shift = this->mesh().bounding_box().center(); if (!shift.isApprox(Vec3d::Zero())) @@ -1554,6 +1560,9 @@ void ModelVolume::center_geometry_after_creation() const_cast(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } + + if (update_source_offset) + source.mesh_offset = shift; } void ModelVolume::calculate_convex_hull() diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 19f032b1c6..410c2d3ef4 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -392,6 +392,18 @@ class ModelVolume final : public ObjectBase { public: std::string name; + // struct used by reload from disk command to recover data from disk + struct Source + { + std::string input_file; + int object_idx{ -1 }; + int volume_idx{ -1 }; + Vec3d mesh_offset{ Vec3d::Zero() }; + + template void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset); } + }; + Source source; + // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } @@ -440,7 +452,7 @@ public: // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! - void center_geometry_after_creation(); + void center_geometry_after_creation(bool update_source_offset = true); void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; @@ -529,7 +541,7 @@ private: // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : ObjectBase(other), - name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); assert(this->id() == other.id() && this->config.id() == other.config.id()); @@ -537,7 +549,7 @@ private: } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != other.id() && this->config.id() == other.config.id()); @@ -558,8 +570,8 @@ private: } template void load(Archive &ar) { bool has_convex_hull; - ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); - cereal::load_by_value(ar, config); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::load_by_value(ar, config); assert(m_mesh); if (has_convex_hull) { cereal::load_optional(ar, m_convex_hull); @@ -571,8 +583,8 @@ private: } template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); - cereal::save_by_value(ar, config); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::save_by_value(ar, config); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); } diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index dd34438799..42bc6c2f1b 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -136,11 +136,11 @@ Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) if (! grown_env.contains(from)) { // delete second point while the line connecting first to third crosses the // boundaries as many times as the current first to second - while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), grown_env).size() == 1) + while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1) polyline.points.erase(polyline.points.begin() + 1); } if (! grown_env.contains(to)) - while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) + while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1) polyline.points.erase(polyline.points.end() - 2); } diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index ee3b997476..39b07e7d86 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -3,11 +3,6 @@ namespace Slic3r { -MultiPoint::operator Points() const -{ - return this->points; -} - void MultiPoint::scale(double factor) { for (Point &pt : points) @@ -57,18 +52,7 @@ void MultiPoint::rotate(double angle, const Point ¢er) } } -void MultiPoint::reverse() -{ - std::reverse(this->points.begin(), this->points.end()); -} - -Point MultiPoint::first_point() const -{ - return this->points.front(); -} - -double -MultiPoint::length() const +double MultiPoint::length() const { Lines lines = this->lines(); double len = 0; @@ -78,8 +62,7 @@ MultiPoint::length() const return len; } -int -MultiPoint::find_point(const Point &point) const +int MultiPoint::find_point(const Point &point) const { for (const Point &pt : this->points) if (pt == point) @@ -87,21 +70,18 @@ MultiPoint::find_point(const Point &point) const return -1; // not found } -bool -MultiPoint::has_boundary_point(const Point &point) const +bool MultiPoint::has_boundary_point(const Point &point) const { double dist = (point.projection_onto(*this) - point).cast().norm(); return dist < SCALED_EPSILON; } -BoundingBox -MultiPoint::bounding_box() const +BoundingBox MultiPoint::bounding_box() const { return BoundingBox(this->points); } -bool -MultiPoint::has_duplicate_points() const +bool MultiPoint::has_duplicate_points() const { for (size_t i = 1; i < points.size(); ++i) if (points[i-1] == points[i]) @@ -109,8 +89,7 @@ MultiPoint::has_duplicate_points() const return false; } -bool -MultiPoint::remove_duplicate_points() +bool MultiPoint::remove_duplicate_points() { size_t j = 0; for (size_t i = 1; i < points.size(); ++i) { @@ -129,8 +108,7 @@ MultiPoint::remove_duplicate_points() return false; } -bool -MultiPoint::intersection(const Line& line, Point* intersection) const +bool MultiPoint::intersection(const Line& line, Point* intersection) const { Lines lines = this->lines(); for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 38020d6e84..9ff91b5022 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -17,7 +17,8 @@ class MultiPoint public: Points points; - operator Points() const; + operator Points() const { return this->points; } + MultiPoint() {} MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} @@ -32,9 +33,10 @@ public: void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); void rotate(double angle, const Point ¢er); - void reverse(); - Point first_point() const; - virtual Point last_point() const = 0; + void reverse() { std::reverse(this->points.begin(), this->points.end()); } + + const Point& first_point() const { return this->points.front(); } + virtual const Point& last_point() const = 0; virtual Lines lines() const = 0; size_t size() const { return points.size(); } bool empty() const { return points.empty(); } diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 82e992fd64..da469b7ba2 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -13,21 +13,28 @@ public: {} ~MutablePriorityQueue() { clear(); } - inline void clear() { m_heap.clear(); } - inline void reserve(size_t cnt) { m_heap.reserve(cnt); } - inline void push(const T &item); - inline void push(T &&item); - inline void pop(); - inline T& top() { return m_heap.front(); } - inline void remove(size_t idx); - inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); } + void clear(); + void reserve(size_t cnt) { m_heap.reserve(cnt); } + void push(const T &item); + void push(T &&item); + void pop(); + T& top() { return m_heap.front(); } + void remove(size_t idx); + void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); } - inline size_t size() const { return m_heap.size(); } - inline bool empty() const { return m_heap.empty(); } + size_t size() const { return m_heap.size(); } + bool empty() const { return m_heap.empty(); } + + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + iterator begin() { return m_heap.begin(); } + iterator end() { return m_heap.end(); } + const_iterator cbegin() const { return m_heap.cbegin(); } + const_iterator cend() const { return m_heap.cend(); } protected: - inline void update_heap_up(size_t top, size_t bottom); - inline void update_heap_down(size_t top, size_t bottom); + void update_heap_up(size_t top, size_t bottom); + void update_heap_down(size_t top, size_t bottom); private: std::vector m_heap; @@ -42,6 +49,17 @@ MutablePriorityQueue make_mutable_priority_queue( std::forward(index_setter), std::forward(less_predicate)); } +template +inline void MutablePriorityQueue::clear() +{ +#ifndef NDEBUG + for (size_t idx = 0; idx < m_heap.size(); ++ idx) + // Mark as removed from the queue. + m_index_setter(m_heap[idx], std::numeric_limits::max()); +#endif /* NDEBUG */ + m_heap.clear(); +} + template inline void MutablePriorityQueue::push(const T &item) { @@ -64,6 +82,10 @@ template inline void MutablePriorityQueue::pop() { assert(! m_heap.empty()); +#ifndef NDEBUG + // Mark as removed from the queue. + m_index_setter(m_heap.front(), std::numeric_limits::max()); +#endif /* NDEBUG */ if (m_heap.size() > 1) { m_heap.front() = m_heap.back(); m_heap.pop_back(); @@ -77,6 +99,10 @@ template inline void MutablePriorityQueue::remove(size_t idx) { assert(idx < m_heap.size()); +#ifndef NDEBUG + // Mark as removed from the queue. + m_index_setter(m_heap[idx], std::numeric_limits::max()); +#endif /* NDEBUG */ if (idx + 1 == m_heap.size()) { m_heap.pop_back(); return; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 0c16f4a1d4..450fff3515 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1,6 +1,8 @@ #include "PerimeterGenerator.hpp" #include "ClipperUtils.hpp" #include "ExtrusionEntityCollection.hpp" +#include "ShortestPath.hpp" + #include #include @@ -86,24 +88,24 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi return paths; } -static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow) +static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector &out) { // This value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount // of segments, and any pruning shall be performed before we apply this tolerance. - ExtrusionEntityCollection coll; const float tolerance = float(scale_(0.05)); for (const ThickPolyline &p : polylines) { ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance); // Append paths to collection. if (! paths.empty()) { if (paths.front().first_point() == paths.back().last_point()) - coll.append(ExtrusionLoop(std::move(paths))); - else - coll.append(std::move(paths)); + out.emplace_back(new ExtrusionLoop(std::move(paths))); + else { + for (ExtrusionPath &path : paths) + out.emplace_back(new ExtrusionPath(std::move(path))); + } } } - return coll; } // Hierarchy of perimeters. @@ -173,10 +175,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime perimeter_generator.overhang_flow.width, perimeter_generator.overhang_flow.height); - // reapply the nearest point search for starting point - // We allow polyline reversal because Clipper may have randomly - // reversed polylines during clipping. - paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path(); + // Reapply the nearest point search for starting point. + // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. + chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); } else { ExtrusionPath path(role); path.polyline = loop.polygon.split_at_first_point(); @@ -186,43 +187,47 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime paths.push_back(path); } - coll.append(ExtrusionLoop(paths, loop_role)); + coll.append(ExtrusionLoop(std::move(paths), loop_role)); } // Append thin walls to the nearest-neighbor search (only for first iteration) if (! thin_walls.empty()) { - ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow); - coll.append(tw.entities); + variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); thin_walls.clear(); } - // Sort entities into a new collection using a nearest-neighbor search, - // preserving the original indices which are useful for detecting thin walls. - ExtrusionEntityCollection sorted_coll; - coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices); - - // traverse children and build the final collection - ExtrusionEntityCollection entities; - for (const size_t &idx : sorted_coll.orig_indices) { - if (idx >= loops.size()) { - // This is a thin wall. Let's get it from the sorted collection as it might have been reversed. - entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()])); + // Traverse children and build the final collection. + Point zero_point(0, 0); + std::vector> chain = chain_extrusion_entities(coll.entities, &zero_point); + ExtrusionEntityCollection out; + for (const std::pair &idx : chain) { + assert(coll.entities[idx.first] != nullptr); + if (idx.first >= loops.size()) { + // This is a thin wall. + out.entities.reserve(out.entities.size() + 1); + out.entities.emplace_back(coll.entities[idx.first]); + coll.entities[idx.first] = nullptr; + if (idx.second) + out.entities.back()->reverse(); } else { - const PerimeterGeneratorLoop &loop = loops[idx]; - ExtrusionLoop eloop = *dynamic_cast(coll.entities[idx]); + const PerimeterGeneratorLoop &loop = loops[idx.first]; + assert(thin_walls.empty()); ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); + out.entities.reserve(out.entities.size() + children.entities.size() + 1); + ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); + coll.entities[idx.first] = nullptr; if (loop.is_contour) { - eloop.make_counter_clockwise(); - entities.append(std::move(children.entities)); - entities.append(std::move(eloop)); + eloop->make_counter_clockwise(); + out.append(std::move(children.entities)); + out.entities.emplace_back(eloop); } else { - eloop.make_clockwise(); - entities.append(std::move(eloop)); - entities.append(std::move(children.entities)); + eloop->make_clockwise(); + out.entities.emplace_back(eloop); + out.append(std::move(children.entities)); } } } - return entities; + return out; } void PerimeterGenerator::process() @@ -445,8 +450,8 @@ void PerimeterGenerator::process() for (const ExPolygon &ex : gaps_ex) ex.medial_axis(max, min, &polylines); if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow); - this->gap_fill->append(gap_fill.entities); + ExtrusionEntityCollection gap_fill; + variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). Growing actual extrusions ensures that gaps not filled by medial axis @@ -456,7 +461,8 @@ void PerimeterGenerator::process() //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, // therefore it may cover the area, but no the volume. last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f)); - } + this->gap_fill->append(std::move(gap_fill.entities)); + } } // create one more offset to be used as boundary for fill diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 8cd71e6974..c0d9e65a78 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -3,7 +3,6 @@ #include "libslic3r.h" #include -#include "ExPolygonCollection.hpp" #include "Flow.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" @@ -15,7 +14,7 @@ class PerimeterGenerator { public: // Inputs: const SurfaceCollection *slices; - const ExPolygonCollection *lower_slices; + const ExPolygons *lower_slices; double layer_height; int layer_id; Flow perimeter_flow; @@ -45,7 +44,7 @@ public: ExtrusionEntityCollection* gap_fill, // Infills without the gap fills SurfaceCollection* fill_surfaces) - : slices(slices), lower_slices(NULL), layer_height(layer_height), + : slices(slices), lower_slices(nullptr), layer_height(layer_height), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), solid_infill_flow(flow), config(config), object_config(object_config), print_config(print_config), diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 994f45e59d..dced5c02a7 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -38,6 +38,7 @@ typedef std::vector PointPtrs; typedef std::vector PointConstPtrs; typedef std::vector Points3; typedef std::vector Pointfs; +typedef std::vector Vec2ds; typedef std::vector Pointf3s; typedef Eigen::Matrix Matrix2f; @@ -87,12 +88,13 @@ class Point : public Vec2crd public: typedef coord_t coord_type; - Point() : Vec2crd() { (*this)(0) = 0; (*this)(1) = 0; } - Point(coord_t x, coord_t y) { (*this)(0) = x; (*this)(1) = y; } - Point(int64_t x, int64_t y) { (*this)(0) = coord_t(x); (*this)(1) = coord_t(y); } // for Clipper - Point(double x, double y) { (*this)(0) = coord_t(lrint(x)); (*this)(1) = coord_t(lrint(y)); } + Point() : Vec2crd(0, 0) {} + Point(coord_t x, coord_t y) : Vec2crd(x, y) {} + Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} // for Clipper + Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {} Point(const Point &rhs) { *this = rhs; } - // This constructor allows you to construct Point from Eigen expressions + explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {} + // This constructor allows you to construct Point from Eigen expressions template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } @@ -126,6 +128,36 @@ public: Point projection_onto(const Line &line) const; }; +inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON)) +{ + Point d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON)) +{ + Vec2f d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON) +{ + Vec2d d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON)) +{ + Vec3f d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; +} + +inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON) +{ + Vec3d d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; +} + namespace int128 { // Exact orientation predicate, // returns +1: CCW, 0: collinear, -1: CW. diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index cf0783bae5..e1e2991444 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -5,55 +5,23 @@ namespace Slic3r { -Polygon::operator Polygons() const -{ - Polygons pp; - pp.push_back(*this); - return pp; -} - -Polygon::operator Polyline() const -{ - return this->split_at_first_point(); -} - -Point& -Polygon::operator[](Points::size_type idx) -{ - return this->points[idx]; -} - -const Point& -Polygon::operator[](Points::size_type idx) const -{ - return this->points[idx]; -} - -Point -Polygon::last_point() const -{ - return this->points.front(); // last point == first point for polygons -} - Lines Polygon::lines() const { return to_lines(*this); } -Polyline -Polygon::split_at_vertex(const Point &point) const +Polyline Polygon::split_at_vertex(const Point &point) const { // find index of point for (const Point &pt : this->points) if (pt == point) - return this->split_at_index(&pt - &this->points.front()); + return this->split_at_index(int(&pt - &this->points.front())); throw std::invalid_argument("Point not found"); return Polyline(); } // Split a closed polygon into an open polyline, with the split point duplicated at both ends. -Polyline -Polygon::split_at_index(int index) const +Polyline Polygon::split_at_index(int index) const { Polyline polyline; polyline.points.reserve(this->points.size() + 1); @@ -64,19 +32,6 @@ Polygon::split_at_index(int index) const return polyline; } -// Split a closed polygon into an open polyline, with the split point duplicated at both ends. -Polyline -Polygon::split_at_first_point() const -{ - return this->split_at_index(0); -} - -Points -Polygon::equally_spaced_points(double distance) const -{ - return this->split_at_first_point().equally_spaced_points(distance); -} - /* int64_t Polygon::area2x() const { @@ -107,20 +62,17 @@ double Polygon::area() const return 0.5 * a; } -bool -Polygon::is_counter_clockwise() const +bool Polygon::is_counter_clockwise() const { return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); } -bool -Polygon::is_clockwise() const +bool Polygon::is_clockwise() const { return !this->is_counter_clockwise(); } -bool -Polygon::make_counter_clockwise() +bool Polygon::make_counter_clockwise() { if (!this->is_counter_clockwise()) { this->reverse(); @@ -129,8 +81,7 @@ Polygon::make_counter_clockwise() return false; } -bool -Polygon::make_clockwise() +bool Polygon::make_clockwise() { if (this->is_counter_clockwise()) { this->reverse(); @@ -139,16 +90,9 @@ Polygon::make_clockwise() return false; } -bool -Polygon::is_valid() const -{ - return this->points.size() >= 3; -} - // Does an unoriented polygon contain a point? // Tested by counting intersections along a horizontal line. -bool -Polygon::contains(const Point &point) const +bool Polygon::contains(const Point &point) const { // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html bool result = false; @@ -174,8 +118,7 @@ Polygon::contains(const Point &point) const } // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() -Polygons -Polygon::simplify(double tolerance) const +Polygons Polygon::simplify(double tolerance) const { // repeat first point at the end in order to apply Douglas-Peucker // on the whole polygon @@ -189,8 +132,7 @@ Polygon::simplify(double tolerance) const return simplify_polygons(pp); } -void -Polygon::simplify(double tolerance, Polygons &polygons) const +void Polygon::simplify(double tolerance, Polygons &polygons) const { Polygons pp = this->simplify(tolerance); polygons.reserve(polygons.size() + pp.size()); @@ -198,8 +140,7 @@ Polygon::simplify(double tolerance, Polygons &polygons) const } // Only call this on convex polygons or it will return invalid results -void -Polygon::triangulate_convex(Polygons* polygons) const +void Polygon::triangulate_convex(Polygons* polygons) const { for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) { Polygon p; @@ -214,8 +155,7 @@ Polygon::triangulate_convex(Polygons* polygons) const } // center of mass -Point -Polygon::centroid() const +Point Polygon::centroid() const { double area_temp = this->area(); double x_temp = 0; @@ -232,20 +172,19 @@ Polygon::centroid() const // find all concave vertices (i.e. having an internal angle greater than the supplied angle) // (external = right side, thus we consider ccw orientation) -Points -Polygon::concave_points(double angle) const +Points Polygon::concave_points(double angle) const { Points points; - angle = 2*PI - angle; + angle = 2. * PI - angle + EPSILON; // check whether first point forms a concave angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) points.push_back(this->points.front()); // check whether points 1..(n-1) form concave angles - for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points.push_back(*p); - } + for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++ p) + if (p->ccw_angle(*(p-1), *(p+1)) <= angle) + points.push_back(*p); // check whether last point forms a concave angle if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) @@ -256,11 +195,10 @@ Polygon::concave_points(double angle) const // find all convex vertices (i.e. having an internal angle smaller than the supplied angle) // (external = right side, thus we consider ccw orientation) -Points -Polygon::convex_points(double angle) const +Points Polygon::convex_points(double angle) const { Points points; - angle = 2*PI - angle; + angle = 2*PI - angle - EPSILON; // check whether first point forms a convex angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) @@ -316,6 +254,11 @@ Point Polygon::point_projection(const Point &point) const return proj; } +BoundingBox get_extents(const Points &points) +{ + return BoundingBox(points); +} + BoundingBox get_extents(const Polygon &poly) { return poly.bounding_box(); @@ -456,4 +399,45 @@ bool remove_small(Polygons &polys, double min_area) return modified; } +void remove_collinear(Polygon &poly) +{ + if (poly.points.size() > 2) { + // copy points and append both 1 and last point in place to cover the boundaries + Points pp; + pp.reserve(poly.points.size()+2); + pp.push_back(poly.points.back()); + pp.insert(pp.begin()+1, poly.points.begin(), poly.points.end()); + pp.push_back(poly.points.front()); + // delete old points vector. Will be re-filled in the loop + poly.points.clear(); + + size_t i = 0; + size_t k = 0; + while (i < pp.size()-2) { + k = i+1; + const Point &p1 = pp[i]; + while (k < pp.size()-1) { + const Point &p2 = pp[k]; + const Point &p3 = pp[k+1]; + Line l(p1, p3); + if(l.distance_to(p2) < SCALED_EPSILON) { + k++; + } else { + if(i > 0) poly.points.push_back(p1); // implicitly removes the first point we appended above + i = k; + break; + } + } + if(k > pp.size()-2) break; // all remaining points are collinear and can be skipped + } + poly.points.push_back(pp[i]); + } +} + +void remove_collinear(Polygons &polys) +{ + for (Polygon &poly : polys) + remove_collinear(poly); +} + } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 63162d9539..8230b49f8a 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -13,15 +13,17 @@ namespace Slic3r { class Polygon; typedef std::vector Polygons; -class Polygon : public MultiPoint { +class Polygon : public MultiPoint +{ public: - operator Polygons() const; - operator Polyline() const; - Point& operator[](Points::size_type idx); - const Point& operator[](Points::size_type idx) const; - + operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } + operator Polyline() const { return this->split_at_first_point(); } + Point& operator[](Points::size_type idx) { return this->points[idx]; } + const Point& operator[](Points::size_type idx) const { return this->points[idx]; } + Polygon() {} - explicit Polygon(const Points &points): MultiPoint(points) {} + explicit Polygon(const Points &points) : MultiPoint(points) {} + Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {} static Polygon new_scale(const std::vector &points) { @@ -34,20 +36,24 @@ public: Polygon& operator=(const Polygon &other) { points = other.points; return *this; } Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } - Point last_point() const; + // last point == first point for polygons + const Point& last_point() const override { return this->points.front(); } + virtual Lines lines() const; Polyline split_at_vertex(const Point &point) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. - Polyline split_at_first_point() const; - Points equally_spaced_points(double distance) const; + Polyline split_at_first_point() const { return this->split_at_index(0); } + Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); } + double area() const; bool is_counter_clockwise() const; bool is_clockwise() const; bool make_counter_clockwise(); bool make_clockwise(); - bool is_valid() const; + bool is_valid() const { return this->points.size() >= 3; } + // Does an unoriented polygon contain a point? // Tested by counting intersections along a horizontal line. bool contains(const Point &point) const; @@ -61,6 +67,10 @@ public: Point point_projection(const Point &point) const; }; +inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; } +inline bool operator!=(const Polygon &lhs, const Polygon &rhs) { return lhs.points != rhs.points; } + +extern BoundingBox get_extents(const Points &points); extern BoundingBox get_extents(const Polygon &poly); extern BoundingBox get_extents(const Polygons &polygons); extern BoundingBox get_extents_rotated(const Polygon &poly, double angle); @@ -81,6 +91,8 @@ extern bool remove_sticks(Polygons &polys); // Remove polygons with less than 3 edges. extern bool remove_degenerate(Polygons &polys); extern bool remove_small(Polygons &polys, double min_area); +extern void remove_collinear(Polygon &poly); +extern void remove_collinear(Polygons &polys); // Append a vector of polygons at the end of another vector of polygons. inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); } @@ -95,6 +107,15 @@ inline void polygons_append(Polygons &dst, Polygons &&src) } } +inline Polygons polygons_simplify(const Polygons &polys, double tolerance) +{ + Polygons out; + out.reserve(polys.size()); + for (const Polygon &p : polys) + polygons_append(out, p.simplify(tolerance)); + return out; +} + inline void polygons_rotate(Polygons &polys, double angle) { const double cos_angle = cos(angle); diff --git a/src/libslic3r/PolygonTrimmer.cpp b/src/libslic3r/PolygonTrimmer.cpp index 3e3c9b4982..2c4e06fc58 100644 --- a/src/libslic3r/PolygonTrimmer.cpp +++ b/src/libslic3r/PolygonTrimmer.cpp @@ -12,12 +12,11 @@ TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid) TrimmedLoop out; if (loop.size() >= 2) { - size_t cnt = loop.points.size(); struct Visitor { Visitor(const EdgeGrid::Grid &grid, const Slic3r::Point *pt_prev, const Slic3r::Point *pt_this) : grid(grid), pt_prev(pt_prev), pt_this(pt_this) {} - void operator()(coord_t iy, coord_t ix) { + bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { @@ -27,6 +26,8 @@ TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid) // The two segments intersect. Add them to the output. } } + // Continue traversing the grid along the edge. + return true; } const EdgeGrid::Grid &grid; diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index af155468ab..26aad83d2b 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -23,24 +23,17 @@ Polyline::operator Line() const return Line(this->points.front(), this->points.back()); } -Point -Polyline::last_point() const +const Point& Polyline::leftmost_point() const { - return this->points.back(); -} - -Point -Polyline::leftmost_point() const -{ - Point p = this->points.front(); - for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { - if ((*it)(0) < p(0)) p = *it; + const Point *p = &this->points.front(); + for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++ it) { + if (it->x() < p->x()) + p = &(*it); } - return p; + return *p; } -Lines -Polyline::lines() const +Lines Polyline::lines() const { Lines lines; if (this->points.size() >= 2) { @@ -211,6 +204,20 @@ BoundingBox get_extents(const Polylines &polylines) return bb; } +const Point& leftmost_point(const Polylines &polylines) +{ + if (polylines.empty()) + throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); + Polylines::const_iterator it = polylines.begin(); + const Point *p = &it->leftmost_point(); + for (++ it; it != polylines.end(); ++it) { + const Point *p2 = &it->leftmost_point(); + if (p2->x() < p->x()) + p = p2; + } + return *p; +} + bool remove_degenerate(Polylines &polylines) { bool modified = false; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index e17fafd622..7e3a1e5065 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -62,8 +62,9 @@ public: operator Polylines() const; operator Line() const; - Point last_point() const; - Point leftmost_point() const; + const Point& last_point() const override { return this->points.back(); } + + const Point& leftmost_point() const; virtual Lines lines() const; void clip_end(double distance); void clip_start(double distance); @@ -76,6 +77,15 @@ public: bool is_straight() const; }; +// Don't use this class in production code, it is used exclusively by the Perl binding for unit tests! +#ifdef PERL_UCHAR_MIN +class PolylineCollection +{ +public: + Polylines polylines; +}; +#endif /* PERL_UCHAR_MIN */ + extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polylines &polylines); @@ -128,6 +138,8 @@ inline void polylines_append(Polylines &dst, Polylines &&src) } } +const Point& leftmost_point(const Polylines &polylines); + bool remove_degenerate(Polylines &polylines); class ThickPolyline : public Polyline { diff --git a/src/libslic3r/PolylineCollection.cpp b/src/libslic3r/PolylineCollection.cpp deleted file mode 100644 index 0c66c371a9..0000000000 --- a/src/libslic3r/PolylineCollection.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "PolylineCollection.hpp" - -namespace Slic3r { - -struct Chaining -{ - Point first; - Point last; - size_t idx; -}; - -template -inline int nearest_point_index(const std::vector &pairs, const Point &start_near, bool no_reverse) -{ - T dmin = std::numeric_limits::max(); - int idx = 0; - for (std::vector::const_iterator it = pairs.begin(); it != pairs.end(); ++it) { - T d = sqr(T(start_near(0) - it->first(0))); - if (d <= dmin) { - d += sqr(T(start_near(1) - it->first(1))); - if (d < dmin) { - idx = (it - pairs.begin()) * 2; - dmin = d; - if (dmin < EPSILON) - break; - } - } - if (! no_reverse) { - d = sqr(T(start_near(0) - it->last(0))); - if (d <= dmin) { - d += sqr(T(start_near(1) - it->last(1))); - if (d < dmin) { - idx = (it - pairs.begin()) * 2 + 1; - dmin = d; - if (dmin < EPSILON) - break; - } - } - } - } - return idx; -} - -Polylines PolylineCollection::_chained_path_from( - const Polylines &src, - Point start_near, - bool no_reverse, - bool move_from_src) -{ - std::vector endpoints; - endpoints.reserve(src.size()); - for (size_t i = 0; i < src.size(); ++ i) { - Chaining c; - c.first = src[i].first_point(); - if (! no_reverse) - c.last = src[i].last_point(); - c.idx = i; - endpoints.push_back(c); - } - Polylines retval; - while (! endpoints.empty()) { - // find nearest point - int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); - assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2); - if (move_from_src) { - retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); - } else { - retval.push_back(src[endpoints[endpoint_index/2].idx]); - } - if (endpoint_index & 1) - retval.back().reverse(); - endpoints.erase(endpoints.begin() + endpoint_index/2); - start_near = retval.back().last_point(); - } - return retval; -} - -Point PolylineCollection::leftmost_point(const Polylines &polylines) -{ - if (polylines.empty()) - throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); - Polylines::const_iterator it = polylines.begin(); - Point p = it->leftmost_point(); - for (++ it; it != polylines.end(); ++it) { - Point p2 = it->leftmost_point(); - if (p2(0) < p(0)) - p = p2; - } - return p; -} - -} // namespace Slic3r diff --git a/src/libslic3r/PolylineCollection.hpp b/src/libslic3r/PolylineCollection.hpp deleted file mode 100644 index 87fc1985b8..0000000000 --- a/src/libslic3r/PolylineCollection.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef slic3r_PolylineCollection_hpp_ -#define slic3r_PolylineCollection_hpp_ - -#include "libslic3r.h" -#include "Polyline.hpp" - -namespace Slic3r { - -class PolylineCollection -{ - static Polylines _chained_path_from( - const Polylines &src, - Point start_near, - bool no_reverse, - bool move_from_src); - -public: - Polylines polylines; - void chained_path(PolylineCollection* retval, bool no_reverse = false) const - { retval->polylines = chained_path(this->polylines, no_reverse); } - void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const - { retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); } - Point leftmost_point() const - { return leftmost_point(polylines); } - void append(const Polylines &polylines) - { this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); } - - static Point leftmost_point(const Polylines &polylines); - static Polylines chained_path(Polylines &&src, bool no_reverse = false) { - return (src.empty() || src.front().points.empty()) ? - Polylines() : - _chained_path_from(src, src.front().first_point(), no_reverse, true); - } - static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false) - { return _chained_path_from(src, start_near, no_reverse, true); } - static Polylines chained_path(const Polylines &src, bool no_reverse = false) { - return (src.empty() || src.front().points.empty()) ? - Polylines() : - _chained_path_from(src, src.front().first_point(), no_reverse, false); - } - static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false) - { return _chained_path_from(src, start_near, no_reverse, false); } -}; - -} - -#endif diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b275af2f2c..e044bdf5c2 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -7,6 +7,7 @@ #include "Flow.hpp" #include "Geometry.hpp" #include "I18N.hpp" +#include "ShortestPath.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" @@ -142,10 +143,7 @@ bool Print::invalidate_state_by_config_options(const std::vector steps_ignore; @@ -166,7 +164,10 @@ bool Print::invalidate_state_by_config_options(const std::vectorm_layers) { - layer->slices.simplify(distance); - for (LayerRegion *layerm : layer->regions()) - layerm->slices.simplify(distance); - } - } -} - double Print::max_allowed_layer_height() const { double nozzle_diameter_max = 0.; @@ -1113,6 +1104,9 @@ std::string Print::validate() const if (m_objects.empty()) return L("All objects are outside of the print volume."); + if (extruders().empty()) + return L("The supplied settings will cause an empty print."); + if (m_config.complete_objects) { // Check horizontal clearance. { @@ -1193,6 +1187,8 @@ std::string Print::validate() const return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); if (m_config.ooze_prevention) return L("Ooze prevention is currently not supported with the wipe tower enabled."); + if (m_config.use_volumetric_e) + return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)."); if (m_objects.size() > 1) { bool has_custom_layering = false; @@ -1270,10 +1266,7 @@ std::string Print::validate() const } { - // find the smallest nozzle diameter std::vector extruders = this->extruders(); - if (extruders.empty()) - return L("The supplied settings will cause an empty print."); // Find the smallest used nozzle diameter and the number of unique nozzle diameters. double min_nozzle_diameter = std::numeric_limits::max(); @@ -1512,6 +1505,14 @@ void Print::process() obj->infill(); for (PrintObject *obj : m_objects) obj->generate_support_material(); + if (this->set_started(psWipeTower)) { + m_wipe_tower_data.clear(); + if (this->has_wipe_tower()) { + //this->set_status(95, L("Generating wipe tower")); + this->_make_wipe_tower(); + } + this->set_done(psWipeTower); + } if (this->set_started(psSkirt)) { m_skirt.clear(); if (this->has_skirt()) { @@ -1528,14 +1529,6 @@ void Print::process() } this->set_done(psBrim); } - if (this->set_started(psWipeTower)) { - m_wipe_tower_data.clear(); - if (this->has_wipe_tower()) { - //this->set_status(95, L("Generating wipe tower")); - this->_make_wipe_tower(); - } - this->set_done(psWipeTower); - } BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } @@ -1543,7 +1536,11 @@ void Print::process() // The export_gcode may die for various reasons (fails to process output_filename_format, // write error into the G-code, cannot execute post-processing scripts). // It is up to the caller to show an error message. +#if ENABLE_THUMBNAIL_GENERATOR +std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector* thumbnail_data) +#else std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) +#endif // ENABLE_THUMBNAIL_GENERATOR { // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. @@ -1560,7 +1557,11 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa // The following line may die for multiple reasons. GCode gcode; +#if ENABLE_THUMBNAIL_GENERATOR + gcode.do_export(this, path.c_str(), preview_data, thumbnail_data); +#else gcode.do_export(this, path.c_str(), preview_data); +#endif // ENABLE_THUMBNAIL_GENERATOR return path.c_str(); } @@ -1592,7 +1593,7 @@ void Print::_make_skirt() for (const Layer *layer : object->m_layers) { if (layer->print_z > skirt_height_z) break; - for (const ExPolygon &expoly : layer->slices.expolygons) + for (const ExPolygon &expoly : layer->slices) // Collect the outer contour points only, ignore holes for the calculation of the convex hull. append(object_points, expoly.contour.points); } @@ -1612,6 +1613,17 @@ void Print::_make_skirt() } } + // Include the wipe tower. + if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { + double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; + double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; + Vec2d pt = Vec2d(m_config.wipe_tower_x-m_wipe_tower_data.brim_width, m_config.wipe_tower_y-m_wipe_tower_data.brim_width); + points.push_back(Point(scale_(pt.x()), scale_(pt.y()))); + points.push_back(Point(scale_(pt.x()+width), scale_(pt.y()))); + points.push_back(Point(scale_(pt.x()+width), scale_(pt.y()+depth))); + points.push_back(Point(scale_(pt.x()), scale_(pt.y()+depth))); + } + if (points.size() < 3) // At least three points required for a convex hull. return; @@ -1703,7 +1715,7 @@ void Print::_make_brim() Polygons islands; for (PrintObject *object : m_objects) { Polygons object_islands; - for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons) + for (ExPolygon &expoly : object->m_layers.front()->slices) object_islands.push_back(expoly.contour); if (! object->support_layers().empty()) object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); @@ -1824,8 +1836,8 @@ void Print::_make_brim() [](const std::pair &l, const std::pair &r) { return l.second < r.second; }); - Vec3f last_pt(0.f, 0.f, 0.f); + Point last_pt(0, 0); for (size_t i = 0; i < loops_trimmed_order.size();) { // Find all pieces that the initial loop was split into. size_t j = i + 1; @@ -1841,16 +1853,23 @@ void Print::_make_brim() points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); i = j; } else { - //FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance. + //FIXME The path chaining here may not be optimal. + ExtrusionEntityCollection this_loop_trimmed; + this_loop_trimmed.entities.reserve(j - i); for (; i < j; ++ i) { - m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); + this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; - Points &points = static_cast(m_brim.entities.back())->polyline.points; + Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); for (const ClipperLib_Z::IntPoint &pt : path) points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); } + chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt); + m_brim.entities.reserve(m_brim.entities.size() + this_loop_trimmed.entities.size()); + append(m_brim.entities, std::move(this_loop_trimmed.entities)); + this_loop_trimmed.entities.clear(); } + last_pt = m_brim.last_point(); } } } else { @@ -1867,6 +1886,22 @@ bool Print::has_wipe_tower() const m_config.nozzle_diameter.values.size() > 1; } +const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_layer_height, double nozzle_diameter) const +{ + // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. + if (! is_step_done(psWipeTower) && extruders_cnt !=0) { + + float width = m_config.wipe_tower_width; + float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4); + + const_cast(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1); + const_cast(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing; + } + + return m_wipe_tower_data; +} + + void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); @@ -1975,6 +2010,7 @@ void Print::_make_wipe_tower() m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); wipe_tower.generate(m_wipe_tower_data.tool_changes); m_wipe_tower_data.depth = wipe_tower.get_depth(); + m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); // Unload the current filament over the purge tower. coordf_t layer_height = m_objects.front()->config().layer_height.value; @@ -2028,6 +2064,7 @@ DynamicConfig PrintStatistics::config() const config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.)); config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume)); config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost)); + config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges)); config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight)); config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost)); config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament)); @@ -2040,7 +2077,7 @@ DynamicConfig PrintStatistics::placeholders() for (const std::string &key : { "print_time", "normal_print_time", "silent_print_time", "used_filament", "extruded_volume", "total_cost", "total_weight", - "total_wipe_tower_cost", "total_wipe_tower_filament"}) + "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"}) config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); return config; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 89a5f3e745..4fcd671665 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -19,6 +19,9 @@ class PrintObject; class ModelObject; class GCode; class GCodePreviewData; +#if ENABLE_THUMBNAIL_GENERATOR +struct ThumbnailData; +#endif // ENABLE_THUMBNAIL_GENERATOR // Print step IDs for keeping track of the print state. enum PrintStep { @@ -96,6 +99,7 @@ public: const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Transform3d& trafo() const { return m_trafo; } const Points& copies() const { return m_copies; } + const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); } // since the object is aligned to origin, bounding box coincides with size BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } @@ -179,7 +183,6 @@ private: void _slice(const std::vector &layer_height_profile); std::string _fix_slicing_errors(); void _simplify_slices(double distance); - void _make_perimeters(); bool has_support_material() const; void detect_surfaces_type(); void process_external_surfaces(); @@ -226,6 +229,7 @@ struct WipeTowerData // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; + float brim_width; void clear() { tool_ordering.clear(); @@ -235,6 +239,7 @@ struct WipeTowerData used_filament.clear(); number_of_toolchanges = -1; depth = 0.f; + brim_width = 0.f; } }; @@ -248,6 +253,7 @@ struct PrintStatistics double total_used_filament; double total_extruded_volume; double total_cost; + int total_toolchanges; double total_weight; double total_wipe_tower_cost; double total_wipe_tower_filament; @@ -268,6 +274,7 @@ struct PrintStatistics total_used_filament = 0.; total_extruded_volume = 0.; total_cost = 0.; + total_toolchanges = 0; total_weight = 0.; total_wipe_tower_cost = 0.; total_wipe_tower_filament = 0.; @@ -303,7 +310,11 @@ public: void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). +#if ENABLE_THUMBNAIL_GENERATOR + std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector* thumbnail_data = nullptr); +#else std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data); +#endif // ENABLE_THUMBNAIL_GENERATOR // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } @@ -314,7 +325,6 @@ public: bool has_infinite_skirt() const; bool has_skirt() const; - float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; } // Returns an empty string if valid, otherwise returns an error message. std::string validate() const override; @@ -353,7 +363,7 @@ public: // Wipe tower support. bool has_wipe_tower() const; - const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; } + const WipeTowerData& wipe_tower_data(size_t extruders_cnt = 0, double first_layer_height = 0., double nozzle_diameter = 0.) const; std::string output_filename(const std::string &filename_base = std::string()) const override; @@ -382,7 +392,6 @@ private: void _make_skirt(); void _make_brim(); void _make_wipe_tower(); - void _simplify_slices(double distance); // Declared here to have access to Model / ModelObject / ModelInstance static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f8f17d1c3a..c8d71be61d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -29,7 +29,7 @@ PrintConfigDef::PrintConfigDef() this->init_common_params(); assign_printer_technology_to_unknown(this->options, ptAny); this->init_fff_params(); - this->init_extruder_retract_keys(); + this->init_extruder_option_keys(); assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); assign_printer_technology_to_unknown(this->options, ptSLA); @@ -433,6 +433,7 @@ void PrintConfigDef::init_fff_params() "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " "If expressed as percentage (for example 200%), it will be computed over layer height."); def->sidetext = L("mm or %"); + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -541,6 +542,7 @@ void PrintConfigDef::init_fff_params() "(see the tooltips for perimeter extrusion width, infill extrusion width etc). " "If expressed as percentage (for example: 230%), it will be computed over layer height."); def->sidetext = L("mm or %"); + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -749,6 +751,10 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionStrings { "" }); def->cli = ConfigOptionDef::nocli; + def = this->add("filament_vendor", coString); + def->set_default_value(new ConfigOptionString(L("(Unknown)"))); + def->cli = ConfigOptionDef::nocli; + def = this->add("fill_angle", coFloat); def->label = L("Fill angle"); def->category = L("Infill"); @@ -859,6 +865,7 @@ void PrintConfigDef::init_fff_params() "If set to zero, it will use the default extrusion width."); def->sidetext = L("mm or %"); def->ratio_over = "first_layer_height"; + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(200, true)); @@ -990,6 +997,7 @@ void PrintConfigDef::init_fff_params() "You may want to use fatter extrudates to speed up the infill and make your parts stronger. " "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or %"); + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -1319,8 +1327,10 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); + def->enum_values.push_back("flashair"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); + def->enum_labels.push_back("FlashAir"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); @@ -1402,6 +1412,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 200%) it will be computed over layer height."); def->sidetext = L("mm or %"); def->aliases = { "perimeters_extrusion_width" }; + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -1739,6 +1750,7 @@ void PrintConfigDef::init_fff_params() "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or %"); + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -1913,6 +1925,7 @@ void PrintConfigDef::init_fff_params() "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or %"); + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -2072,6 +2085,7 @@ void PrintConfigDef::init_fff_params() "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or %"); + def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -2266,8 +2280,17 @@ void PrintConfigDef::init_fff_params() } } -void PrintConfigDef::init_extruder_retract_keys() +void PrintConfigDef::init_extruder_option_keys() { + // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings + m_extruder_option_keys = { + "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", + "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "default_filament_profile" + }; + m_extruder_retract_keys = { "deretract_speed", "retract_before_travel", @@ -2392,11 +2415,24 @@ void PrintConfigDef::init_sla_params() "the threshold in the middle. This behaviour eliminates " "antialiasing without losing holes in polygons."); def->min = 0; + def->max = 1; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(1.0)); // SLA Material settings. + def = this->add("material_type", coString); + def->label = L("SLA material type"); + def->tooltip = L("SLA material type"); + def->gui_type = "f_enum_open"; // TODO: ??? + def->gui_flags = "show_value"; + def->enum_values.push_back("Tough"); + def->enum_values.push_back("Flexible"); + def->enum_values.push_back("Casting"); + def->enum_values.push_back("Dental"); + def->enum_values.push_back("Heat-resistant"); + def->set_default_value(new ConfigOptionString("Tough")); + def = this->add("initial_layer_height", coFloat); def->label = L("Initial layer height"); def->tooltip = L("Initial layer height"); @@ -2502,6 +2538,10 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); + def = this->add("material_vendor", coString); + def->set_default_value(new ConfigOptionString(L("(Unknown)"))); + def->cli = ConfigOptionDef::nocli; + def = this->add("default_sla_material_profile", coString); def->label = L("Default SLA material profile"); def->tooltip = L("Default print profile associated with the current printer profile. " @@ -2721,6 +2761,17 @@ void PrintConfigDef::init_sla_params() def->max = 30; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("pad_brim_size", coFloat); + def->label = L("Pad brim size"); + def->tooltip = L("How far should the pad extend around the contained geometry"); + def->category = L("Pad"); + // def->tooltip = L(""); + def->sidetext = L("mm"); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.6)); def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); @@ -2761,6 +2812,13 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Create pad around object and ignore the support elevation"); def->mode = comSimple; def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("pad_around_object_everywhere", coBool); + def->label = L("Pad around object everywhere"); + def->category = L("Pad"); + def->tooltip = L("Force pad around object everywhere"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); def = this->add("pad_object_gap", coFloat); def->label = L("Pad object gap"); @@ -2878,9 +2936,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va const PrintConfigDef print_config_def; -DynamicPrintConfig* DynamicPrintConfig::new_from_defaults() +DynamicPrintConfig DynamicPrintConfig::full_print_config() +{ + return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults()); +} + +DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfig(rhs, rhs.keys_ref()) { - return new_from_defaults_keys(FullPrintConfig::defaults().keys()); } DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector &keys) @@ -2927,6 +2989,20 @@ void DynamicPrintConfig::normalize() } } +void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) +{ + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : print_config_def.extruder_option_keys()) { + if (key == "default_filament_profile") + continue; + auto *opt = this->option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + static_cast(opt)->resize(num_extruders, defaults.option(key)); + } +} + std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e92ddabac4..75ee7a3994 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -30,7 +30,7 @@ enum GCodeFlavor : unsigned char { }; enum PrintHostType { - htOctoPrint, htDuet + htOctoPrint, htDuet, htFlashAir }; enum InfillPattern { @@ -52,6 +52,14 @@ enum FilamentType { }; */ +enum SLAMaterial { + slamTough, + slamFlex, + slamCasting, + slamDental, + slamHeatResistant, +}; + enum SLADisplayOrientation { sladoLandscape, sladoPortrait @@ -94,6 +102,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g if (keys_map.empty()) { keys_map["octoprint"] = htOctoPrint; keys_map["duet"] = htDuet; + keys_map["flashair"] = htFlashAir; } return keys_map; } @@ -185,6 +194,8 @@ public: static void handle_legacy(t_config_option_key &opt_key, std::string &value); + // Array options growing with the number of extruders + const std::vector& extruder_option_keys() const { return m_extruder_option_keys; } // Options defining the extruder retract properties. These keys are sorted lexicographically. // The extruder retract keys could be overidden by the same values defined at the Filament level // (then the key is further prefixed with the "filament_" prefix). @@ -193,9 +204,10 @@ public: private: void init_common_params(); void init_fff_params(); - void init_extruder_retract_keys(); + void init_extruder_option_keys(); void init_sla_params(); + std::vector m_extruder_option_keys; std::vector m_extruder_retract_keys; }; @@ -203,6 +215,8 @@ private: // This definition is constant. extern const PrintConfigDef print_config_def; +class StaticPrintConfig; + // Slic3r dynamic configuration, used to override the configuration // per object, per modification volume or per printing material. // The dynamic configuration is also used to store user modifications of the print global parameters, @@ -213,9 +227,11 @@ class DynamicPrintConfig : public DynamicConfig { public: DynamicPrintConfig() {} - DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} + DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {} + explicit DynamicPrintConfig(const StaticPrintConfig &rhs); + explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {} - static DynamicPrintConfig* new_from_defaults(); + static DynamicPrintConfig full_print_config(); static DynamicPrintConfig* new_from_defaults_keys(const std::vector &keys); // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. @@ -223,6 +239,8 @@ public: void normalize(); + void set_num_extruders(unsigned int num_extruders); + // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); @@ -249,6 +267,8 @@ public: // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } + // Reference to the cached list of keys. + virtual const t_config_option_keys& keys_ref() const = 0; protected: // Verify whether the opt_key has not been obsoleted or renamed. @@ -337,6 +357,7 @@ public: \ { return s_cache_##CLASS_NAME.optptr(opt_key, this); } \ /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \ + const t_config_option_keys& keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \ static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \ private: \ static void initialize_cache() \ @@ -1022,6 +1043,9 @@ public: // The height of the pad from the bottom to the top not considering the pit ConfigOptionFloat pad_wall_height /*= 5*/; + + // How far should the pad extend around the contained geometry + ConfigOptionFloat pad_brim_size; // The greatest distance where two individual pads are merged into one. The // distance is measured roughly from the centroids of the pads. @@ -1042,7 +1066,9 @@ public: // ///////////////////////////////////////////////////////////////////////// // Disable the elevation (ignore its value) and use the zero elevation mode - ConfigOptionBool pad_around_object; + ConfigOptionBool pad_around_object; + + ConfigOptionBool pad_around_object_everywhere; // This is the gap between the object bottom and the generated pad ConfigOptionFloat pad_object_gap; @@ -1082,10 +1108,12 @@ protected: OPT_PTR(pad_enable); OPT_PTR(pad_wall_thickness); OPT_PTR(pad_wall_height); + OPT_PTR(pad_brim_size); OPT_PTR(pad_max_merge_distance); // OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); OPT_PTR(pad_around_object); + OPT_PTR(pad_around_object_everywhere); OPT_PTR(pad_object_gap); OPT_PTR(pad_object_connector_stride); OPT_PTR(pad_object_connector_width); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 44f1ef875f..c4ca46a8c5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,6 +1,7 @@ #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "ElephantFootCompensation.hpp" #include "Geometry.hpp" #include "I18N.hpp" #include "SupportMaterial.hpp" @@ -12,7 +13,6 @@ #include #include -#include #include #include @@ -75,13 +75,9 @@ PrintBase::ApplyStatus PrintObject::set_copies(const Points &points) { // Order copies with a nearest-neighbor search. std::vector copies; - { - std::vector ordered_copies; - Slic3r::Geometry::chained_path(points, ordered_copies); - copies.reserve(ordered_copies.size()); - for (size_t point_idx : ordered_copies) - copies.emplace_back(points[point_idx] + m_copies_shift); - } + copies.reserve(points.size()); + for (const Point &pt : points) + copies.emplace_back(pt + m_copies_shift); // Invalidate and set copies. PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; if (copies != m_copies) { @@ -122,6 +118,19 @@ void PrintObject::slice() // Simplify slices if required. if (m_print->config().resolution) this->_simplify_slices(scale_(this->print()->config().resolution)); + // Update bounding boxes + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + Layer &layer = *m_layers[layer_idx]; + layer.slices_bboxes.clear(); + layer.slices_bboxes.reserve(layer.slices.size()); + for (const ExPolygon &expoly : layer.slices) + layer.slices_bboxes.emplace_back(get_extents(expoly)); + } + }); if (m_layers.empty()) throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); @@ -870,7 +879,7 @@ void PrintObject::process_external_surfaces() // Shrink the holes, let the layer above expand slightly inside the unsupported areas. polygons_append(voids, offset(surface.expolygon, unsupported_width)); } - surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->slices.expolygons), voids); + surfaces_covered[layer_idx] = diff(to_polygons(this->m_layers[layer_idx]->slices), voids); } } ); @@ -980,8 +989,8 @@ void PrintObject::discover_vertical_shells() polygons_append(cache.holes, offset(offset_ex(layer.slices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices.expolygons)); - svg.draw(layer.slices.expolygons, "blue"); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices)); + svg.draw(layer.slices, "blue"); svg.draw(union_ex(cache.holes), "red"); svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05)); svg.Close(); @@ -1664,25 +1673,26 @@ void PrintObject::_slice(const std::vector &layer_height_profile) // Trim volumes in a single layer, one by the other, possibly apply upscaling. { Polygons processed; - for (SlicedVolume &sliced_volume : sliced_volumes) { - ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]); - if (upscale) - slices = offset_ex(std::move(slices), delta); - if (! processed.empty()) - // Trim by the slices of already processed regions. - slices = diff_ex(to_polygons(std::move(slices)), processed); - if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size()) - // Collect the already processed regions to trim the to be processed regions. - polygons_append(processed, slices); - sliced_volume.expolygons_by_layer[layer_id] = std::move(slices); - } + for (SlicedVolume &sliced_volume : sliced_volumes) + if (! sliced_volume.expolygons_by_layer.empty()) { + ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]); + if (upscale) + slices = offset_ex(std::move(slices), delta); + if (! processed.empty()) + // Trim by the slices of already processed regions. + slices = diff_ex(to_polygons(std::move(slices)), processed); + if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size()) + // Collect the already processed regions to trim the to be processed regions. + polygons_append(processed, slices); + sliced_volume.expolygons_by_layer[layer_id] = std::move(slices); + } } // Collect and union volumes of a single region. for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { ExPolygons expolygons; size_t num_volumes = 0; for (SlicedVolume &sliced_volume : sliced_volumes) - if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer[layer_id].empty()) { + if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer.empty() && ! sliced_volume.expolygons_by_layer[layer_id].empty()) { ++ num_volumes; append(expolygons, std::move(sliced_volume.expolygons_by_layer[layer_id])); } @@ -1760,8 +1770,10 @@ end: Layer *layer = m_layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(m_config.xy_size_compensation.value)); + //FIXME only apply the compensation if no raft is enabled. float elephant_foot_compensation = 0.f; - if (layer_id == 0) + if (layer_id == 0 && m_config.raft_layers == 0) + // Only enable Elephant foot compensation if printing directly on the print bed. elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value)); if (layer->m_regions.size() == 1) { // Optimized version for a single region layer. @@ -1780,19 +1792,8 @@ end: to_expolygons(std::move(layerm->slices.surfaces)) : offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta); // Apply the elephant foot compensation. - if (elephant_foot_compensation > 0) { - float elephant_foot_spacing = float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing()); - float external_perimeter_nozzle = float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1))); - // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. - float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); - size_t nsteps = size_t(steps); - float step = elephant_foot_compensation / steps; - for (size_t i = 0; i < nsteps; ++ i) { - Polygons tmp = offset(expolygons, - step); - append(tmp, diff(to_polygons(expolygons), offset(offset_ex(expolygons, -elephant_foot_spacing - step), elephant_foot_spacing + step))); - expolygons = union_ex(tmp); - } - } + if (elephant_foot_compensation > 0) + expolygons = union_ex(Slic3r::elephant_foot_compensation(expolygons, layerm->flow(frExternalPerimeter), unscale(elephant_foot_compensation))); layerm->slices.set(std::move(expolygons), stInternal); } } else { @@ -1816,33 +1817,18 @@ end: layerm->slices.set(std::move(slices), stInternal); } } - if (delta < 0.f) { + if (delta < 0.f || elephant_foot_compensation > 0.f) { // Apply the negative XY compensation. - Polygons trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON)); + Polygons trimming; + static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5); + if (elephant_foot_compensation > 0.f) { + trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(eps), std::min(delta, 0.f) - eps), + layer->m_regions.front()->flow(frExternalPerimeter), unscale(elephant_foot_compensation))); + } else + trimming = offset(layer->merged(float(SCALED_EPSILON)), delta - float(SCALED_EPSILON)); for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) layer->m_regions[region_id]->trim_surfaces(trimming); } - if (elephant_foot_compensation > 0.f) { - // Apply the elephant foot compensation. - std::vector elephant_foot_spacing; - elephant_foot_spacing.reserve(layer->m_regions.size()); - float external_perimeter_nozzle = 0.f; - for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { - LayerRegion *layerm = layer->m_regions[region_id]; - elephant_foot_spacing.emplace_back(float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing())); - external_perimeter_nozzle += float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1))); - } - external_perimeter_nozzle /= (float)layer->m_regions.size(); - // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. - float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); - size_t nsteps = size_t(steps); - float step = elephant_foot_compensation / steps; - for (size_t i = 0; i < nsteps; ++ i) { - Polygons trimming_polygons = offset(layer->merged(float(EPSILON)), - step - float(EPSILON)); - for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) - layer->m_regions[region_id]->elephant_foot_compensation_step(elephant_foot_spacing[region_id] + step, trimming_polygons); - } - } } // Merge all regions' slices to get islands, chain them by a shortest path. layer->make_slices(); @@ -2145,7 +2131,7 @@ std::string PrintObject::_fix_slicing_errors() BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end"; // remove empty layers from bottom - while (! m_layers.empty() && m_layers.front()->slices.expolygons.empty()) { + while (! m_layers.empty() && m_layers.front()->slices.empty()) { delete m_layers.front(); m_layers.erase(m_layers.begin()); m_layers.front()->lower_layer = nullptr; @@ -2172,115 +2158,17 @@ void PrintObject::_simplify_slices(double distance) Layer *layer = m_layers[layer_idx]; for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++ region_idx) layer->m_regions[region_idx]->slices.simplify(distance); - layer->slices.simplify(distance); + { + ExPolygons simplified; + for (const ExPolygon& expoly : layer->slices) + expoly.simplify(distance, &simplified); + layer->slices = std::move(simplified); + } } }); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end"; } -void PrintObject::_make_perimeters() -{ - if (! this->set_started(posPerimeters)) - return; - - BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info(); - - // merge slices if they were split into types - if (this->typed_slices) { - for (Layer *layer : m_layers) - layer->merge_slices(); - this->typed_slices = false; - this->invalidate_step(posPrepareInfill); - } - - // compare each layer to the one below, and mark those slices needing - // one additional inner perimeter, like the top of domed objects- - - // this algorithm makes sure that at least one perimeter is overlapping - // but we don't generate any extra perimeter if fill density is zero, as they would be floating - // inside the object - infill_only_where_needed should be the method of choice for printing - // hollow objects - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; - if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2) - continue; - - BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start"; - tbb::parallel_for( - tbb::blocked_range(0, m_layers.size() - 1), - [this, ®ion, region_id](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - LayerRegion &layerm = *m_layers[layer_idx]->regions()[region_id]; - const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->regions()[region_id]; - const Polygons upper_layerm_polygons = upper_layerm.slices; - // Filter upper layer polygons in intersection_ppl by their bounding boxes? - // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; - const double total_loop_length = total_length(upper_layerm_polygons); - const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing(); - const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter); - const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); - const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); - - for (Surface &slice : layerm.slices.surfaces) { - for (;;) { - // compute the total thickness of perimeters - const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 - + (region.config().perimeters-1 + slice.extra_perimeters) * perimeter_spacing; - // define a critical area where we don't want the upper slice to fall into - // (it should either lay over our perimeters or outside this area) - const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5); - const Polygons critical_area = diff( - offset(slice.expolygon, float(- perimeters_thickness)), - offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth)) - ); - // check whether a portion of the upper slices falls inside the critical area - const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area); - // only add an additional loop if at least 30% of the slice loop would benefit from it - if (total_length(intersection) <= total_loop_length*0.3) - break; - /* - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "extra.svg", - no_arrows => 1, - expolygons => union_ex($critical_area), - polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], - ); - } - */ - ++ slice.extra_perimeters; - } - #ifdef DEBUG - if (slice.extra_perimeters > 0) - printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx); - #endif - } - } - }); - BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end"; - } - - BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start"; - tbb::parallel_for( - tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) - m_layers[layer_idx]->make_perimeters(); - } - ); - BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; - - /* - simplify slices (both layer and region slices), - we only need the max resolution for perimeters - ### This makes this method not-idempotent, so we keep it disabled for now. - ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); - */ - - this->set_done(posPerimeters); -} - // Only active if config->infill_only_where_needed. This step trims the sparse infill, // so it acts as an internal support. It maintains all other infill types intact. // Here the internal surfaces and perimeters have to be supported by the sparse infill. @@ -2306,7 +2194,7 @@ void PrintObject::clip_fill_surfaces() // Detect things that we need to support. // Cummulative slices. Polygons slices; - polygons_append(slices, layer->slices.expolygons); + polygons_append(slices, layer->slices); // Cummulative fill surfaces. Polygons fill_surfaces; // Solid surfaces to be supported. diff --git a/src/libslic3r/SLA/ConcaveHull.cpp b/src/libslic3r/SLA/ConcaveHull.cpp new file mode 100644 index 0000000000..dff0617216 --- /dev/null +++ b/src/libslic3r/SLA/ConcaveHull.cpp @@ -0,0 +1,171 @@ +#include "ConcaveHull.hpp" +#include +#include +#include "SLASpatIndex.hpp" +#include + +namespace Slic3r { +namespace sla { + +inline Vec3d to_vec3(const Vec2crd &v2) { return {double(v2(X)), double(v2(Y)), 0.}; } +inline Vec3d to_vec3(const Vec2d &v2) { return {v2(X), v2(Y), 0.}; } +inline Vec2crd to_vec2(const Vec3d &v3) { return {coord_t(v3(X)), coord_t(v3(Y))}; } + +Point ConcaveHull::centroid(const Points &pp) +{ + Point c; + switch(pp.size()) { + case 0: break; + case 1: c = pp.front(); break; + case 2: c = (pp[0] + pp[1]) / 2; break; + default: { + auto MAX = std::numeric_limits::max(); + auto MIN = std::numeric_limits::min(); + Point min = {MAX, MAX}, max = {MIN, MIN}; + + for(auto& p : pp) { + if(p(0) < min(0)) min(0) = p(0); + if(p(1) < min(1)) min(1) = p(1); + if(p(0) > max(0)) max(0) = p(0); + if(p(1) > max(1)) max(1) = p(1); + } + c(0) = min(0) + (max(0) - min(0)) / 2; + c(1) = min(1) + (max(1) - min(1)) / 2; + break; + } + } + + return c; +} + +// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound +// mode +ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths, + coord_t delta, + ClipperLib::JoinType jointype) +{ + using ClipperLib::ClipperOffset; + using ClipperLib::etClosedPolygon; + using ClipperLib::Paths; + using ClipperLib::Path; + + ClipperOffset offs; + offs.ArcTolerance = scaled(0.01); + + for (auto &p : paths) + // If the input is not at least a triangle, we can not do this algorithm + if(p.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; + return {}; + } + + offs.AddPaths(paths, jointype, etClosedPolygon); + + Paths result; + offs.Execute(result, static_cast(delta)); + + return result; +} + +Points ConcaveHull::calculate_centroids() const +{ + // We get the centroids of all the islands in the 2D slice + Points centroids = reserve_vector(m_polys.size()); + std::transform(m_polys.begin(), m_polys.end(), + std::back_inserter(centroids), + [this](const Polygon &poly) { return centroid(poly); }); + + return centroids; +} + +void ConcaveHull::merge_polygons() { m_polys = get_contours(union_ex(m_polys)); } + +void ConcaveHull::add_connector_rectangles(const Points ¢roids, + coord_t max_dist, + ThrowOnCancel thr) +{ + // Centroid of the centroids of islands. This is where the additional + // connector sticks are routed. + Point cc = centroid(centroids); + + PointIndex ctrindex; + unsigned idx = 0; + for(const Point &ct : centroids) ctrindex.insert(to_vec3(ct), idx++); + + m_polys.reserve(m_polys.size() + centroids.size()); + + idx = 0; + for (const Point &c : centroids) { + thr(); + + double dx = c.x() - cc.x(), dy = c.y() - cc.y(); + double l = std::sqrt(dx * dx + dy * dy); + double nx = dx / l, ny = dy / l; + + const Point &ct = centroids[idx]; + + std::vector result = ctrindex.nearest(to_vec3(ct), 2); + + double dist = max_dist; + for (const PointIndexEl &el : result) + if (el.second != idx) { + dist = Line(to_vec2(el.first), ct).length(); + break; + } + + idx++; + + if (dist >= max_dist) return; + + Polygon r; + r.points.reserve(3); + r.points.emplace_back(cc); + + Point n(scaled(nx), scaled(ny)); + r.points.emplace_back(c + Point(n.y(), -n.x())); + r.points.emplace_back(c + Point(-n.y(), n.x())); + offset(r, scaled(1.)); + + m_polys.emplace_back(r); + } +} + +ConcaveHull::ConcaveHull(const Polygons &polys, double mergedist, ThrowOnCancel thr) +{ + if(polys.empty()) return; + + m_polys = polys; + merge_polygons(); + + if(m_polys.size() == 1) return; + + Points centroids = calculate_centroids(); + + add_connector_rectangles(centroids, scaled(mergedist), thr); + + merge_polygons(); +} + +ExPolygons ConcaveHull::to_expolygons() const +{ + auto ret = reserve_vector(m_polys.size()); + for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p)); + return ret; +} + +ExPolygons offset_waffle_style_ex(const ConcaveHull &hull, coord_t delta) +{ + ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(hull.polygons()); + paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound); + paths = fast_offset(paths, -delta, ClipperLib::jtRound); + ExPolygons ret = ClipperPaths_to_Slic3rExPolygons(paths); + for (ExPolygon &p : ret) p.holes = {}; + return ret; +} + +Polygons offset_waffle_style(const ConcaveHull &hull, coord_t delta) +{ + return to_polygons(offset_waffle_style_ex(hull, delta)); +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/ConcaveHull.hpp b/src/libslic3r/SLA/ConcaveHull.hpp new file mode 100644 index 0000000000..94e16d77cc --- /dev/null +++ b/src/libslic3r/SLA/ConcaveHull.hpp @@ -0,0 +1,53 @@ +#ifndef CONCAVEHULL_HPP +#define CONCAVEHULL_HPP + +#include + +namespace Slic3r { +namespace sla { + +inline Polygons get_contours(const ExPolygons &poly) +{ + Polygons ret; ret.reserve(poly.size()); + for (const ExPolygon &p : poly) ret.emplace_back(p.contour); + + return ret; +} + +using ThrowOnCancel = std::function; + +/// A fake concave hull that is constructed by connecting separate shapes +/// with explicit bridges. Bridges are generated from each shape's centroid +/// to the center of the "scene" which is the centroid calculated from the shape +/// centroids (a star is created...) +class ConcaveHull { + Polygons m_polys; + + static Point centroid(const Points& pp); + + static inline Point centroid(const Polygon &poly) { return poly.centroid(); } + + Points calculate_centroids() const; + + void merge_polygons(); + + void add_connector_rectangles(const Points ¢roids, + coord_t max_dist, + ThrowOnCancel thr); +public: + + ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr) + : ConcaveHull{to_polygons(polys), merge_dist, thr} {} + + ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr); + + const Polygons & polygons() const { return m_polys; } + + ExPolygons to_expolygons() const; +}; + +ExPolygons offset_waffle_style_ex(const ConcaveHull &ccvhull, coord_t delta); +Polygons offset_waffle_style(const ConcaveHull &polys, coord_t delta); + +}} // namespace Slic3r::sla +#endif // CONCAVEHULL_HPP diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 36378df395..65f5901431 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -16,6 +16,7 @@ #include namespace Slic3r { +namespace sla { /*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) { @@ -48,9 +49,16 @@ float SLAAutoSupports::distance_limit(float angle) const return 1./(2.4*get_required_density(angle)); }*/ -SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, - const Config& config, std::function throw_on_cancel, std::function statusfn) -: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn) +SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh, + const std::vector &slices, + const std::vector & heights, + const Config & config, + std::function throw_on_cancel, + std::function statusfn) + : m_config(config) + , m_emesh(emesh) + , m_throw_on_cancel(throw_on_cancel) + , m_statusfn(statusfn) { process(slices, heights); project_onto_mesh(m_output); @@ -505,6 +513,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru } } +void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance) +{ + // get iterator to the reorganized vector end + auto endit = + std::remove_if(pts.begin(), pts.end(), + [tolerance, gnd_lvl](const sla::SupportPoint &sp) { + double diff = std::abs(gnd_lvl - + double(sp.pos(Z))); + return diff <= tolerance; + }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); +} + #ifdef SLA_AUTOSUPPORTS_DEBUG void SLAAutoSupports::output_structures(const std::vector& structures) { @@ -533,4 +556,5 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::st } #endif +} // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 38f21e6cff..d2f50f0a46 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -11,20 +11,22 @@ // #define SLA_AUTOSUPPORTS_DEBUG namespace Slic3r { +namespace sla { class SLAAutoSupports { public: struct Config { - float density_relative; - float minimal_distance; - float head_diameter; + float density_relative {1.f}; + float minimal_distance {1.f}; + float head_diameter {0.4f}; /////////////// inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit) inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; - SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, + SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, const Config& config, std::function throw_on_cancel, std::function statusfn); + const std::vector& output() { return m_output; } struct MyLayer; @@ -199,7 +201,9 @@ private: std::function m_statusfn; }; +void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance); +} // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp deleted file mode 100644 index 53dfef4045..0000000000 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ /dev/null @@ -1,922 +0,0 @@ -#include "SLABasePool.hpp" -#include "SLABoilerPlate.hpp" - -#include "boost/log/trivial.hpp" -#include "SLABoostAdapter.hpp" -#include "ClipperUtils.hpp" -#include "Tesselate.hpp" -#include "MTUtils.hpp" - -// For debugging: -// #include -// #include -// #include "SVG.hpp" - -namespace Slic3r { namespace sla { - -/// This function will return a triangulation of a sheet connecting an upper -/// and a lower plate given as input polygons. It will not triangulate the -/// plates themselves only the sheet. The caller has to specify the lower and -/// upper z levels in world coordinates as well as the offset difference -/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the -/// offset difference is negative, the resulting triangle orientation will be -/// reversed. -/// -/// IMPORTANT: This is not a universal triangulation algorithm. It assumes -/// that the lower and upper polygons are offsetted versions of the same -/// original polygon. In general, it assumes that one of the polygons is -/// completely inside the other. The offset difference is the reference -/// distance from the inner polygon's perimeter to the outer polygon's -/// perimeter. The real distance will be variable as the clipper offset has -/// different strategies (rounding, etc...). This algorithm should have -/// O(2n + 3m) complexity where n is the number of upper vertices and m is the -/// number of lower vertices. -Contour3D walls(const Polygon& lower, const Polygon& upper, - double lower_z_mm, double upper_z_mm, - double offset_difference_mm, ThrowOnCancel thr) -{ - Contour3D ret; - - if(upper.points.size() < 3 || lower.size() < 3) return ret; - - // The concept of the algorithm is relatively simple. It will try to find - // the closest vertices from the upper and the lower polygon and use those - // as starting points. Then it will create the triangles sequentially using - // an edge from the upper polygon and a vertex from the lower or vice versa, - // depending on the resulting triangle's quality. - // The quality is measured by a scalar value. So far it looks like it is - // enough to derive it from the slope of the triangle's two edges connecting - // the upper and the lower part. A reference slope is calculated from the - // height and the offset difference. - - // Offset in the index array for the ceiling - const auto offs = upper.points.size(); - - // Shorthand for the vertex arrays - auto& upoints = upper.points, &lpoints = lower.points; - auto& rpts = ret.points; auto& ind = ret.indices; - - // If the Z levels are flipped, or the offset difference is negative, we - // will interpret that as the triangles normals should be inverted. - bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; - - // Copy the points into the mesh, convert them from 2D to 3D - rpts.reserve(upoints.size() + lpoints.size()); - ind.reserve(2 * upoints.size() + 2 * lpoints.size()); - for (auto &p : upoints) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); - for (auto &p : lpoints) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); - - // Create pointing indices into vertex arrays. u-upper, l-lower - size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; - - // Simple squared distance calculation. - auto distfn = [](const Vec3d& p1, const Vec3d& p2) { - auto p = p1 - p2; return p.transpose() * p; - }; - - // We need to find the closest point on lower polygon to the first point on - // the upper polygon. These will be our starting points. - double distmin = std::numeric_limits::max(); - for(size_t l = lidx; l < rpts.size(); ++l) { - thr(); - double d = distfn(rpts[l], rpts[uidx]); - if(d < distmin) { lidx = l; distmin = d; } - } - - // Set up lnextidx to be ahead of lidx in cyclic mode - lnextidx = lidx + 1; - if(lnextidx == rpts.size()) lnextidx = offs; - - // This will be the flip switch to toggle between upper and lower triangle - // creation mode - enum class Proceed { - UPPER, // A segment from the upper polygon and one vertex from the lower - LOWER // A segment from the lower polygon and one vertex from the upper - } proceed = Proceed::UPPER; - - // Flags to help evaluating loop termination. - bool ustarted = false, lstarted = false; - - // The variables for the fitness values, one for the actual and one for the - // previous. - double current_fit = 0, prev_fit = 0; - - // Every triangle of the wall has two edges connecting the upper plate with - // the lower plate. From the length of these two edges and the zdiff we - // can calculate the momentary squared offset distance at a particular - // position on the wall. The average of the differences from the reference - // (squared) offset distance will give us the driving fitness value. - const double offsdiff2 = std::pow(offset_difference_mm, 2); - const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); - - // Mark the current vertex iterator positions. If the iterators return to - // the same position, the loop can be terminated. - size_t uendidx = uidx, lendidx = lidx; - - do { thr(); // check throw if canceled - - prev_fit = current_fit; - - switch(proceed) { // proceed depending on the current state - case Proceed::UPPER: - if(!ustarted || uidx != uendidx) { // there are vertices remaining - // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[uidx]; - const Vec3d& p_low = rpts[lidx]; - const Vec3d& p_up2 = rpts[unextidx]; - - // Calculate fitness: the average of the two connecting edges - double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); - double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { // fit is worse than previously - proceed = Proceed::LOWER; - } else { // good to go, create the triangle - inverted - ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) - : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); - - // Increment the iterators, rotate if necessary - ++uidx; ++unextidx; - if(unextidx == offs) unextidx = 0; - if(uidx == offs) uidx = 0; - - ustarted = true; // mark the movement of the iterators - // so that the comparison to uendidx can be made correctly - } - } else proceed = Proceed::LOWER; - - break; - case Proceed::LOWER: - // Mode with lower segment, upper vertex. Same structure: - if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[lidx]; - const Vec3d& p_low2 = rpts[lnextidx]; - const Vec3d& p_up = rpts[uidx]; - - double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); - double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { - proceed = Proceed::UPPER; - } else { - inverted - ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) - : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); - - ++lidx; ++lnextidx; - if(lnextidx == rpts.size()) lnextidx = offs; - if(lidx == rpts.size()) lidx = offs; - - lstarted = true; - } - } else proceed = Proceed::UPPER; - - break; - } // end of switch - } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); - - return ret; -} - -/// Offsetting with clipper and smoothing the edges into a curvature. -void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { - using ClipperLib::ClipperOffset; - using ClipperLib::jtRound; - using ClipperLib::jtMiter; - using ClipperLib::etClosedPolygon; - using ClipperLib::Paths; - using ClipperLib::Path; - - auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh.contour); - auto&& holes = Slic3rMultiPoints_to_ClipperPaths(sh.holes); - - // If the input is not at least a triangle, we can not do this algorithm - if(ctour.size() < 3 || - std::any_of(holes.begin(), holes.end(), - [](const Path& p) { return p.size() < 3; }) - ) { - BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; - return; - } - - auto jointype = edgerounding? jtRound : jtMiter; - - ClipperOffset offs; - offs.ArcTolerance = scaled(0.01); - Paths result; - offs.AddPath(ctour, jointype, etClosedPolygon); - offs.AddPaths(holes, jointype, etClosedPolygon); - offs.Execute(result, static_cast(distance)); - - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. - - bool found_the_contour = false; - sh.holes.clear(); - for(auto& r : result) { - if(ClipperLib::Orientation(r)) { - // We don't like if the offsetting generates more than one contour - // but throwing would be an overkill. Instead, we should warn the - // caller about the inability to create correct geometries - if(!found_the_contour) { - auto rr = ClipperPath_to_Slic3rPolygon(r); - sh.contour.points.swap(rr.points); - found_the_contour = true; - } else { - BOOST_LOG_TRIVIAL(warning) - << "Warning: offsetting result is invalid!"; - } - } else { - // TODO If there are multiple contours we can't be sure which hole - // belongs to the first contour. (But in this case the situation is - // bad enough to let it go...) - sh.holes.emplace_back(ClipperPath_to_Slic3rPolygon(r)); - } - } -} - -void offset(Polygon &sh, coord_t distance, bool edgerounding = true) -{ - using ClipperLib::ClipperOffset; - using ClipperLib::jtRound; - using ClipperLib::jtMiter; - using ClipperLib::etClosedPolygon; - using ClipperLib::Paths; - using ClipperLib::Path; - - auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); - - // If the input is not at least a triangle, we can not do this algorithm - if (ctour.size() < 3) { - BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; - return; - } - - ClipperOffset offs; - offs.ArcTolerance = 0.01 * scaled(1.); - Paths result; - offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); - offs.Execute(result, static_cast(distance)); - - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. - - bool found_the_contour = false; - for (auto &r : result) { - if (ClipperLib::Orientation(r)) { - // We don't like if the offsetting generates more than one contour - // but throwing would be an overkill. Instead, we should warn the - // caller about the inability to create correct geometries - if (!found_the_contour) { - auto rr = ClipperPath_to_Slic3rPolygon(r); - sh.points.swap(rr.points); - found_the_contour = true; - } else { - BOOST_LOG_TRIVIAL(warning) - << "Warning: offsetting result is invalid!"; - } - } - } -} - -/// Unification of polygons (with clipper) preserving holes as well. -ExPolygons unify(const ExPolygons& shapes) { - using ClipperLib::ptSubject; - - ExPolygons retv; - - bool closed = true; - bool valid = true; - - ClipperLib::Clipper clipper; - - for(auto& path : shapes) { - auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path.contour); - - if(!clipperpath.empty()) - valid &= clipper.AddPath(clipperpath, ptSubject, closed); - - auto clipperholes = Slic3rMultiPoints_to_ClipperPaths(path.holes); - - for(auto& hole : clipperholes) { - if(!hole.empty()) - valid &= clipper.AddPath(hole, ptSubject, closed); - } - } - - if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; - - ClipperLib::PolyTree result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); - - retv.reserve(static_cast(result.Total())); - - // Now we will recursively traverse the polygon tree and serialize it - // into an ExPolygon with holes. The polygon tree has the clipper-ish - // PolyTree structure which alternates its nodes as contours and holes - - // A "declaration" of function for traversing leafs which are holes - std::function processHole; - - // Process polygon which calls processHoles which than calls processPoly - // again until no leafs are left. - auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) { - ExPolygon poly; - poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); - for(auto h : pptr->Childs) { processHole(h, poly); } - retv.push_back(poly); - }; - - // Body of the processHole function - processHole = [&processPoly](ClipperLib::PolyNode *pptr, ExPolygon& poly) - { - poly.holes.emplace_back(); - poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); - for(auto c : pptr->Childs) processPoly(c); - }; - - // Wrapper for traversing. - auto traverse = [&processPoly] (ClipperLib::PolyNode *node) - { - for(auto ch : node->Childs) { - processPoly(ch); - } - }; - - // Here is the actual traverse - traverse(&result); - - return retv; -} - -Polygons unify(const Polygons& shapes) { - using ClipperLib::ptSubject; - - bool closed = true; - bool valid = true; - - ClipperLib::Clipper clipper; - - for(auto& path : shapes) { - auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); - - if(!clipperpath.empty()) - valid &= clipper.AddPath(clipperpath, ptSubject, closed); - } - - if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; - - ClipperLib::Paths result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); - - Polygons ret; - for (ClipperLib::Path &p : result) { - Polygon pp = ClipperPath_to_Slic3rPolygon(p); - if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); - } - - return ret; -} - -// Function to cut tiny connector cavities for a given polygon. The input poly -// will be offsetted by "padding" and small rectangle shaped cavities will be -// inserted along the perimeter in every "stride" distance. The stick rectangles -// will have a with about "stick_width". The input dimensions are in world -// measure, not the scaled clipper units. -void breakstick_holes(ExPolygon& poly, - double padding, - double stride, - double stick_width, - double penetration) -{ - // SVG svg("bridgestick_plate.svg"); - // svg.draw(poly); - - auto transf = [stick_width, penetration, padding, stride](Points &pts) { - // The connector stick will be a small rectangle with dimensions - // stick_width x (penetration + padding) to have some penetration - // into the input polygon. - - Points out; - out.reserve(2 * pts.size()); // output polygon points - - // stick bottom and right edge dimensions - double sbottom = scaled(stick_width); - double sright = scaled(penetration + padding); - - // scaled stride distance - double sstride = scaled(stride); - double t = 0; - - // process pairs of vertices as an edge, start with the last and - // first point - for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { - // Get vertices and the direction vectors - const Point &a = pts[i], &b = pts[j]; - Vec2d dir = b.cast() - a.cast(); - double nrm = dir.norm(); - dir /= nrm; - Vec2d dirp(-dir(Y), dir(X)); - - // Insert start point - out.emplace_back(a); - - // dodge the start point, do not make sticks on the joins - while (t < sbottom) t += sbottom; - double tend = nrm - sbottom; - - while (t < tend) { // insert the stick on the polygon perimeter - - // calculate the stick rectangle vertices and insert them - // into the output. - Point p1 = a + (t * dir).cast(); - Point p2 = p1 + (sright * dirp).cast(); - Point p3 = p2 + (sbottom * dir).cast(); - Point p4 = p3 + (sright * -dirp).cast(); - out.insert(out.end(), {p1, p2, p3, p4}); - - // continue along the perimeter - t += sstride; - } - - t = t - nrm; - - // Insert edge endpoint - out.emplace_back(b); - } - - // move the new points - out.shrink_to_fit(); - pts.swap(out); - }; - - if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { - transf(poly.contour.points); - for (auto &h : poly.holes) transf(h.points); - } - - // svg.draw(poly); - // svg.Close(); -} - -/// This method will create a rounded edge around a flat polygon in 3d space. -/// 'base_plate' parameter is the target plate. -/// 'radius' is the radius of the edges. -/// 'degrees' is tells how much of a circle should be created as the rounding. -/// It should be in degrees, not radians. -/// 'ceilheight_mm' is the Z coordinate of the flat polygon in 3D space. -/// 'dir' Is the direction of the round edges: inward or outward -/// 'thr' Throws if a cancel signal was received -/// 'last_offset' An auxiliary output variable to save the last offsetted -/// version of 'base_plate' -/// 'last_height' An auxiliary output to save the last z coordinate of the -/// offsetted base_plate. In other words, where the rounded edges end. -Contour3D round_edges(const ExPolygon& base_plate, - double radius_mm, - double degrees, - double ceilheight_mm, - bool dir, - ThrowOnCancel thr, - ExPolygon& last_offset, double& last_height) -{ - auto ob = base_plate; - auto ob_prev = ob; - double wh = ceilheight_mm, wh_prev = wh; - Contour3D curvedwalls; - - int steps = 30; - double stepx = radius_mm / steps; - coord_t s = dir? 1 : -1; - degrees = std::fmod(degrees, 180); - - // we use sin for x distance because we interpret the angle starting from - // PI/2 - int tos = degrees < 90? - int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps; - - for(int i = 1; i <= tos; ++i) { - thr(); - - ob = base_plate; - - double r2 = radius_mm * radius_mm; - double xx = i*stepx; - double x2 = xx*xx; - double stepy = std::sqrt(r2 - x2); - - offset(ob, s * scaled(xx)); - wh = ceilheight_mm - radius_mm + stepy; - - Contour3D pwalls; - double prev_x = xx - (i - 1) * stepx; - pwalls = walls(ob.contour, ob_prev.contour, wh, wh_prev, s*prev_x, thr); - - curvedwalls.merge(pwalls); - ob_prev = ob; - wh_prev = wh; - } - - if(degrees > 90) { - double tox = radius_mm - radius_mm*std::cos(degrees * PI / 180 - PI/2); - int tos = int(tox / stepx); - - for(int i = 1; i <= tos; ++i) { - thr(); - ob = base_plate; - - double r2 = radius_mm * radius_mm; - double xx = radius_mm - i*stepx; - double x2 = xx*xx; - double stepy = std::sqrt(r2 - x2); - offset(ob, s * scaled(xx)); - wh = ceilheight_mm - radius_mm - stepy; - - Contour3D pwalls; - double prev_x = xx - radius_mm + (i - 1)*stepx; - pwalls = - walls(ob_prev.contour, ob.contour, wh_prev, wh, s*prev_x, thr); - - curvedwalls.merge(pwalls); - ob_prev = ob; - wh_prev = wh; - } - } - - last_offset = std::move(ob); - last_height = wh; - - return curvedwalls; -} - -inline Point centroid(Points& pp) { - Point c; - switch(pp.size()) { - case 0: break; - case 1: c = pp.front(); break; - case 2: c = (pp[0] + pp[1]) / 2; break; - default: { - auto MAX = std::numeric_limits::max(); - auto MIN = std::numeric_limits::min(); - Point min = {MAX, MAX}, max = {MIN, MIN}; - - for(auto& p : pp) { - if(p(0) < min(0)) min(0) = p(0); - if(p(1) < min(1)) min(1) = p(1); - if(p(0) > max(0)) max(0) = p(0); - if(p(1) > max(1)) max(1) = p(1); - } - c(0) = min(0) + (max(0) - min(0)) / 2; - c(1) = min(1) + (max(1) - min(1)) / 2; - - // TODO: fails for non convex cluster -// c = std::accumulate(pp.begin(), pp.end(), Point{0, 0}); -// x(c) /= coord_t(pp.size()); y(c) /= coord_t(pp.size()); - break; - } - } - - return c; -} - -inline Point centroid(const Polygon& poly) { - return poly.centroid(); -} - -/// A fake concave hull that is constructed by connecting separate shapes -/// with explicit bridges. Bridges are generated from each shape's centroid -/// to the center of the "scene" which is the centroid calculated from the shape -/// centroids (a star is created...) -Polygons concave_hull(const Polygons& polys, double maxd_mm, ThrowOnCancel thr) -{ - namespace bgi = boost::geometry::index; - using SpatElement = std::pair; - using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; - - if(polys.empty()) return Polygons(); - - const double max_dist = scaled(maxd_mm); - - Polygons punion = unify(polys); // could be redundant - - if(punion.size() == 1) return punion; - - // We get the centroids of all the islands in the 2D slice - Points centroids; centroids.reserve(punion.size()); - std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), - [](const Polygon& poly) { return centroid(poly); }); - - SpatIndex ctrindex; - unsigned idx = 0; - for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); - - // Centroid of the centroids of islands. This is where the additional - // connector sticks are routed. - Point cc = centroid(centroids); - - punion.reserve(punion.size() + centroids.size()); - - idx = 0; - std::transform(centroids.begin(), centroids.end(), - std::back_inserter(punion), - [¢roids, &ctrindex, cc, max_dist, &idx, thr] - (const Point& c) - { - thr(); - double dx = x(c) - x(cc), dy = y(c) - y(cc); - double l = std::sqrt(dx * dx + dy * dy); - double nx = dx / l, ny = dy / l; - - Point& ct = centroids[idx]; - - std::vector result; - ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); - - double dist = max_dist; - for (const SpatElement &el : result) - if (el.second != idx) { - dist = Line(el.first, ct).length(); - break; - } - - idx++; - - if (dist >= max_dist) return Polygon(); - - Polygon r; - auto& ctour = r.points; - - ctour.reserve(3); - ctour.emplace_back(cc); - - Point d(scaled(nx), scaled(ny)); - ctour.emplace_back(c + Point( -y(d), x(d) )); - ctour.emplace_back(c + Point( y(d), -x(d) )); - offset(r, scaled(1.)); - - return r; - }); - - // This is unavoidable... - punion = unify(punion); - - return punion; -} - -void base_plate(const TriangleMesh & mesh, - ExPolygons & output, - const std::vector &heights, - ThrowOnCancel thrfn) -{ - if (mesh.empty()) return; - // m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&mesh); - - std::vector out; out.reserve(heights.size()); - slicer.slice(heights, 0.f, &out, thrfn); - - size_t count = 0; for(auto& o : out) count += o.size(); - - // Now we have to unify all slice layers which can be an expensive operation - // so we will try to simplify the polygons - ExPolygons tmp; tmp.reserve(count); - for(ExPolygons& o : out) - for(ExPolygon& e : o) { - auto&& exss = e.simplify(scaled(0.1)); - for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); - } - - ExPolygons utmp = unify(tmp); - - for(auto& o : utmp) { - auto&& smp = o.simplify(scaled(0.1)); - output.insert(output.end(), smp.begin(), smp.end()); - } -} - -void base_plate(const TriangleMesh &mesh, - ExPolygons & output, - float h, - float layerh, - ThrowOnCancel thrfn) -{ - auto bb = mesh.bounding_box(); - float gnd = float(bb.min(Z)); - std::vector heights = {float(bb.min(Z))}; - - for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) - heights.emplace_back(hi); - - base_plate(mesh, output, heights, thrfn); -} - -Contour3D create_base_pool(const Polygons &ground_layer, - const ExPolygons &obj_self_pad = {}, - const PoolConfig& cfg = PoolConfig()) -{ - // for debugging: - // Benchmark bench; - // bench.start(); - - double mergedist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm)+ - cfg.max_merge_distance_mm; - - // Here we get the base polygon from which the pad has to be generated. - // We create an artificial concave hull from this polygon and that will - // serve as the bottom plate of the pad. We will offset this concave hull - // and then offset back the result with clipper with rounding edges ON. This - // trick will create a nice rounded pad shape. - Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); - - const double thickness = cfg.min_wall_thickness_mm; - const double wingheight = cfg.min_wall_height_mm; - const double fullheight = wingheight + thickness; - const double slope = cfg.wall_slope; - const double wingdist = wingheight / std::tan(slope); - const double bottom_offs = (thickness + wingheight) / std::tan(slope); - - // scaled values - const coord_t s_thickness = scaled(thickness); - const coord_t s_eradius = scaled(cfg.edge_radius_mm); - const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); - const coord_t s_wingdist = scaled(wingdist); - const coord_t s_bottom_offs = scaled(bottom_offs); - - auto& thrcl = cfg.throw_on_cancel; - - Contour3D pool; - - for(Polygon& concaveh : concavehs) { - if(concaveh.points.empty()) return pool; - - // Here lies the trick that does the smoothing only with clipper offset - // calls. The offset is configured to round edges. Inner edges will - // be rounded because we offset twice: ones to get the outer (top) plate - // and again to get the inner (bottom) plate - auto outer_base = concaveh; - offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - - ExPolygon bottom_poly; bottom_poly.contour = outer_base; - offset(bottom_poly, -s_bottom_offs); - - // Punching a hole in the top plate for the cavity - ExPolygon top_poly; - ExPolygon middle_base; - ExPolygon inner_base; - top_poly.contour = outer_base; - - if(wingheight > 0) { - inner_base.contour = outer_base; - offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); - - middle_base.contour = outer_base; - offset(middle_base, -s_thickness); - top_poly.holes.emplace_back(middle_base.contour); - auto& tph = top_poly.holes.back().points; - std::reverse(tph.begin(), tph.end()); - } - - ExPolygon ob; ob.contour = outer_base; double wh = 0; - - // now we will calculate the angle or portion of the circle from - // pi/2 that will connect perfectly with the bottom plate. - // this is a tangent point calculation problem and the equation can - // be found for example here: - // http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm - // the y coordinate would be: - // y = cy + (r^2*py - r*px*sqrt(px^2 + py^2 - r^2) / (px^2 + py^2) - // where px and py are the coordinates of the point outside the circle - // cx and cy are the circle center, r is the radius - // We place the circle center to (0, 0) in the calculation the make - // things easier. - // to get the angle we use arcsin function and subtract 90 degrees then - // flip the sign to get the right input to the round_edge function. - double r = cfg.edge_radius_mm; - double cy = 0; - double cx = 0; - double px = thickness + wingdist; - double py = r - fullheight; - - double pxcx = px - cx; - double pycy = py - cy; - double b_2 = pxcx*pxcx + pycy*pycy; - double r_2 = r*r; - double D = std::sqrt(b_2 - r_2); - double vy = (r_2*pycy - r*pxcx*D) / b_2; - double phi = -(std::asin(vy/r) * 180 / PI - 90); - - - // Generate the smoothed edge geometry - if(s_eradius > 0) pool.merge(round_edges(ob, - r, - phi, - 0, // z position of the input plane - true, - thrcl, - ob, wh)); - - // Now that we have the rounded edge connecting the top plate with - // the outer side walls, we can generate and merge the sidewall geometry - pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight, - bottom_offs, thrcl)); - - if(wingheight > 0) { - // Generate the smoothed edge geometry - wh = 0; - ob = middle_base; - if(s_eradius) pool.merge(round_edges(middle_base, - r, - phi - 90, // from tangent lines - 0, // z position of the input plane - false, - thrcl, - ob, wh)); - - // Next is the cavity walls connecting to the top plate's - // artificially created hole. - pool.merge(walls(inner_base.contour, ob.contour, -wingheight, - wh, -wingdist, thrcl)); - } - - if (cfg.embed_object) { - ExPolygons bttms = diff_ex(to_polygons(bottom_poly), - to_polygons(obj_self_pad)); - - assert(!bttms.empty()); - - std::sort(bttms.begin(), bttms.end(), - [](const ExPolygon& e1, const ExPolygon& e2) { - return e1.contour.area() > e2.contour.area(); - }); - - if(wingheight > 0) inner_base.holes = bttms.front().holes; - else top_poly.holes = bttms.front().holes; - - auto straight_walls = - [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { - - auto lines = cntr.lines(); - - for (auto &l : lines) { - auto s = coord_t(pool.points.size()); - auto& pts = pool.points; - pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); - pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); - pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); - pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); - - pool.indices.emplace_back(s, s + 1, s + 3); - pool.indices.emplace_back(s, s + 3, s + 2); - } - }; - - coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); - for (ExPolygon &ep : bttms) { - pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); - for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); - } - - // Skip the outer contour, triangulate the holes - for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { - pool.merge(triangulate_expolygon_3d(*it, -wingheight)); - straight_walls(it->contour, z_lo, z_hi); - } - - } else { - // Now we need to triangulate the top and bottom plates as well as - // the cavity bottom plate which is the same as the bottom plate - // but it is elevated by the thickness. - - pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); - } - - pool.merge(triangulate_expolygon_3d(top_poly)); - - if(wingheight > 0) - pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); - - } - - return pool; -} - -void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, - const ExPolygons &holes, const PoolConfig& cfg) -{ - - - // For debugging: - // bench.stop(); - // std::cout << "Pad creation time: " << bench.getElapsedSec() << std::endl; - // std::fstream fout("pad_debug.obj", std::fstream::out); - // if(fout.good()) pool.to_obj(fout); - - out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); -} - -} -} diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp deleted file mode 100644 index eec426bbf5..0000000000 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef SLABASEPOOL_HPP -#define SLABASEPOOL_HPP - -#include -#include -#include - -namespace Slic3r { - -class ExPolygon; -class Polygon; -using ExPolygons = std::vector; -using Polygons = std::vector; - -class TriangleMesh; - -namespace sla { - -using ThrowOnCancel = std::function; - -/// Calculate the polygon representing the silhouette from the specified height -void base_plate(const TriangleMesh& mesh, // input mesh - ExPolygons& output, // Output will be merged with - float samplingheight = 0.1f, // The height range to sample - float layerheight = 0.05f, // The sampling height - ThrowOnCancel thrfn = [](){}); // Will be called frequently - -void base_plate(const TriangleMesh& mesh, // input mesh - ExPolygons& output, // Output will be merged with - const std::vector&, // Exact Z levels to sample - ThrowOnCancel thrfn = [](){}); // Will be called frequently - -// Function to cut tiny connector cavities for a given polygon. The input poly -// will be offsetted by "padding" and small rectangle shaped cavities will be -// inserted along the perimeter in every "stride" distance. The stick rectangles -// will have a with about "stick_width". The input dimensions are in world -// measure, not the scaled clipper units. -void breakstick_holes(ExPolygon &poly, - double padding, - double stride, - double stick_width, - double penetration = 0.0); - -Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}); - -struct PoolConfig { - double min_wall_thickness_mm = 2; - double min_wall_height_mm = 5; - double max_merge_distance_mm = 50; - double edge_radius_mm = 1; - double wall_slope = std::atan(1.0); // Universal constant for Pi/4 - struct EmbedObject { - double object_gap_mm = 0.5; - double stick_stride_mm = 10; - double stick_width_mm = 0.3; - double stick_penetration_mm = 0.1; - bool enabled = false; - operator bool() const { return enabled; } - } embed_object; - - ThrowOnCancel throw_on_cancel = [](){}; - - inline PoolConfig() {} - inline PoolConfig(double wt, double wh, double md, double er, double slope): - min_wall_thickness_mm(wt), - min_wall_height_mm(wh), - max_merge_distance_mm(md), - edge_radius_mm(er), - wall_slope(slope) {} -}; - -/// Calculate the pool for the mesh for SLA printing -void create_base_pool(const Polygons& base_plate, - TriangleMesh& output_mesh, - const ExPolygons& holes, - const PoolConfig& = PoolConfig()); - -/// Returns the elevation needed for compensating the pad. -inline double get_pad_elevation(const PoolConfig& cfg) { - return cfg.min_wall_thickness_mm; -} - -inline double get_pad_fullheight(const PoolConfig& cfg) { - return cfg.min_wall_height_mm + cfg.min_wall_thickness_mm; -} - -} - -} - -#endif // SLABASEPOOL_HPP diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index 86e90f3b7f..d7ce26bb2b 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -8,35 +8,19 @@ #include #include +#include "SLACommon.hpp" +#include "SLASpatIndex.hpp" + namespace Slic3r { namespace sla { -/// Get x and y coordinates (because we are eigenizing...) -inline coord_t x(const Point& p) { return p(0); } -inline coord_t y(const Point& p) { return p(1); } -inline coord_t& x(Point& p) { return p(0); } -inline coord_t& y(Point& p) { return p(1); } - -inline coordf_t x(const Vec3d& p) { return p(0); } -inline coordf_t y(const Vec3d& p) { return p(1); } -inline coordf_t z(const Vec3d& p) { return p(2); } -inline coordf_t& x(Vec3d& p) { return p(0); } -inline coordf_t& y(Vec3d& p) { return p(1); } -inline coordf_t& z(Vec3d& p) { return p(2); } - -inline coord_t& x(Vec3crd& p) { return p(0); } -inline coord_t& y(Vec3crd& p) { return p(1); } -inline coord_t& z(Vec3crd& p) { return p(2); } -inline coord_t x(const Vec3crd& p) { return p(0); } -inline coord_t y(const Vec3crd& p) { return p(1); } -inline coord_t z(const Vec3crd& p) { return p(2); } - /// Intermediate struct for a 3D mesh struct Contour3D { Pointf3s points; std::vector indices; - void merge(const Contour3D& ctr) { + Contour3D& merge(const Contour3D& ctr) + { auto s3 = coord_t(points.size()); auto s = indices.size(); @@ -44,21 +28,27 @@ struct Contour3D { indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); for(size_t n = s; n < indices.size(); n++) { - auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; + auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3; } + + return *this; } - void merge(const Pointf3s& triangles) { + 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) + + 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) { + void to_obj(std::ostream& stream) + { for(auto& p : points) { stream << "v " << p.transpose() << "\n"; } @@ -72,6 +62,31 @@ struct Contour3D { using ClusterEl = std::vector; using ClusteredPoints = std::vector; +// Clustering a set of points by the given distance. +ClusteredPoints cluster(const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points); + +ClusteredPoints cluster(const PointSet& points, + double dist, + unsigned max_points); + +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function 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 throw_on_cancel = [](){}, + const std::vector& selected_points = {}); + /// Mesh from an existing contour. inline TriangleMesh mesh(const Contour3D& ctour) { return {ctour.points, ctour.indices}; diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index d8e258035e..97b4596761 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -1,8 +1,9 @@ #ifndef SLACOMMON_HPP #define SLACOMMON_HPP -#include #include +#include +#include // #define SLIC3R_SLA_NEEDS_WINDTREE @@ -69,6 +70,8 @@ struct SupportPoint } }; +using SupportPoints = std::vector; + /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree class EigenMesh3D { @@ -175,6 +178,8 @@ public: } }; +using PointSet = Eigen::MatrixXd; + } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLAConcurrency.hpp b/src/libslic3r/SLA/SLAConcurrency.hpp new file mode 100644 index 0000000000..4beb2aead1 --- /dev/null +++ b/src/libslic3r/SLA/SLAConcurrency.hpp @@ -0,0 +1,56 @@ +#ifndef SLACONCURRENCY_H +#define SLACONCURRENCY_H + +#include +#include +#include + +namespace Slic3r { +namespace sla { + +// Set this to true to enable full parallelism in this module. +// Only the well tested parts will be concurrent if this is set to false. +const constexpr bool USE_FULL_CONCURRENCY = true; + +template struct _ccr {}; + +template<> struct _ccr +{ + using SpinningMutex = tbb::spin_mutex; + using BlockingMutex = tbb::mutex; + + template + static inline void enumerate(It from, It to, Fn fn) + { + auto iN = to - from; + size_t N = iN < 0 ? 0 : size_t(iN); + + tbb::parallel_for(size_t(0), N, [from, fn](size_t n) { + fn(*(from + decltype(iN)(n)), n); + }); + } +}; + +template<> struct _ccr +{ +private: + struct _Mtx { inline void lock() {} inline void unlock() {} }; + +public: + using SpinningMutex = _Mtx; + using BlockingMutex = _Mtx; + + template + static inline void enumerate(It from, It to, Fn fn) + { + for (auto it = from; it != to; ++it) fn(*it, size_t(it - from)); + } +}; + +using ccr = _ccr; +using ccr_seq = _ccr; +using ccr_par = _ccr; + +}} // namespace Slic3r::sla + +#endif // SLACONCURRENCY_H diff --git a/src/libslic3r/SLA/SLAPad.cpp b/src/libslic3r/SLA/SLAPad.cpp new file mode 100644 index 0000000000..7cd9eb4e42 --- /dev/null +++ b/src/libslic3r/SLA/SLAPad.cpp @@ -0,0 +1,695 @@ +#include "SLAPad.hpp" +#include "SLABoilerPlate.hpp" +#include "SLASpatIndex.hpp" +#include "ConcaveHull.hpp" + +#include "boost/log/trivial.hpp" +#include "SLABoostAdapter.hpp" +#include "ClipperUtils.hpp" +#include "Tesselate.hpp" +#include "MTUtils.hpp" + +// For debugging: +// #include +// #include +#include "SVG.hpp" + +#include "I18N.hpp" +#include + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) + +namespace Slic3r { namespace sla { + +namespace { + +/// This function will return a triangulation of a sheet connecting an upper +/// and a lower plate given as input polygons. It will not triangulate the +/// plates themselves only the sheet. The caller has to specify the lower and +/// upper z levels in world coordinates as well as the offset difference +/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the +/// offset difference is negative, the resulting triangle orientation will be +/// reversed. +/// +/// IMPORTANT: This is not a universal triangulation algorithm. It assumes +/// that the lower and upper polygons are offsetted versions of the same +/// original polygon. In general, it assumes that one of the polygons is +/// completely inside the other. The offset difference is the reference +/// distance from the inner polygon's perimeter to the outer polygon's +/// perimeter. The real distance will be variable as the clipper offset has +/// different strategies (rounding, etc...). This algorithm should have +/// O(2n + 3m) complexity where n is the number of upper vertices and m is the +/// number of lower vertices. +Contour3D walls( + const Polygon &lower, + const Polygon &upper, + double lower_z_mm, + double upper_z_mm, + double offset_difference_mm, + ThrowOnCancel thr = [] {}) +{ + Contour3D ret; + + if(upper.points.size() < 3 || lower.size() < 3) return ret; + + // The concept of the algorithm is relatively simple. It will try to find + // the closest vertices from the upper and the lower polygon and use those + // as starting points. Then it will create the triangles sequentially using + // an edge from the upper polygon and a vertex from the lower or vice versa, + // depending on the resulting triangle's quality. + // The quality is measured by a scalar value. So far it looks like it is + // enough to derive it from the slope of the triangle's two edges connecting + // the upper and the lower part. A reference slope is calculated from the + // height and the offset difference. + + // Offset in the index array for the ceiling + const auto offs = upper.points.size(); + + // Shorthand for the vertex arrays + auto& upts = upper.points, &lpts = lower.points; + auto& rpts = ret.points; auto& ind = ret.indices; + + // If the Z levels are flipped, or the offset difference is negative, we + // will interpret that as the triangles normals should be inverted. + bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; + + // Copy the points into the mesh, convert them from 2D to 3D + rpts.reserve(upts.size() + lpts.size()); + ind.reserve(2 * upts.size() + 2 * lpts.size()); + for (auto &p : upts) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + for (auto &p : lpts) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); + + // Create pointing indices into vertex arrays. u-upper, l-lower + size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; + + // Simple squared distance calculation. + auto distfn = [](const Vec3d& p1, const Vec3d& p2) { + auto p = p1 - p2; return p.transpose() * p; + }; + + // We need to find the closest point on lower polygon to the first point on + // the upper polygon. These will be our starting points. + double distmin = std::numeric_limits::max(); + for(size_t l = lidx; l < rpts.size(); ++l) { + thr(); + double d = distfn(rpts[l], rpts[uidx]); + if(d < distmin) { lidx = l; distmin = d; } + } + + // Set up lnextidx to be ahead of lidx in cyclic mode + lnextidx = lidx + 1; + if(lnextidx == rpts.size()) lnextidx = offs; + + // This will be the flip switch to toggle between upper and lower triangle + // creation mode + enum class Proceed { + UPPER, // A segment from the upper polygon and one vertex from the lower + LOWER // A segment from the lower polygon and one vertex from the upper + } proceed = Proceed::UPPER; + + // Flags to help evaluating loop termination. + bool ustarted = false, lstarted = false; + + // The variables for the fitness values, one for the actual and one for the + // previous. + double current_fit = 0, prev_fit = 0; + + // Every triangle of the wall has two edges connecting the upper plate with + // the lower plate. From the length of these two edges and the zdiff we + // can calculate the momentary squared offset distance at a particular + // position on the wall. The average of the differences from the reference + // (squared) offset distance will give us the driving fitness value. + const double offsdiff2 = std::pow(offset_difference_mm, 2); + const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); + + // Mark the current vertex iterator positions. If the iterators return to + // the same position, the loop can be terminated. + size_t uendidx = uidx, lendidx = lidx; + + do { thr(); // check throw if canceled + + prev_fit = current_fit; + + switch(proceed) { // proceed depending on the current state + case Proceed::UPPER: + if(!ustarted || uidx != uendidx) { // there are vertices remaining + // Get the 3D vertices in order + const Vec3d& p_up1 = rpts[uidx]; + const Vec3d& p_low = rpts[lidx]; + const Vec3d& p_up2 = rpts[unextidx]; + + // Calculate fitness: the average of the two connecting edges + double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); + double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); + current_fit = (std::abs(a) + std::abs(b)) / 2; + + if(current_fit > prev_fit) { // fit is worse than previously + proceed = Proceed::LOWER; + } else { // good to go, create the triangle + inverted + ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) + : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); + + // Increment the iterators, rotate if necessary + ++uidx; ++unextidx; + if(unextidx == offs) unextidx = 0; + if(uidx == offs) uidx = 0; + + ustarted = true; // mark the movement of the iterators + // so that the comparison to uendidx can be made correctly + } + } else proceed = Proceed::LOWER; + + break; + case Proceed::LOWER: + // Mode with lower segment, upper vertex. Same structure: + if(!lstarted || lidx != lendidx) { + const Vec3d& p_low1 = rpts[lidx]; + const Vec3d& p_low2 = rpts[lnextidx]; + const Vec3d& p_up = rpts[uidx]; + + double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); + double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); + current_fit = (std::abs(a) + std::abs(b)) / 2; + + if(current_fit > prev_fit) { + proceed = Proceed::UPPER; + } else { + inverted + ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) + : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); + + ++lidx; ++lnextidx; + if(lnextidx == rpts.size()) lnextidx = offs; + if(lidx == rpts.size()) lidx = offs; + + lstarted = true; + } + } else proceed = Proceed::UPPER; + + break; + } // end of switch + } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); + + return ret; +} + +// Same as walls() but with identical higher and lower polygons. +Contour3D inline straight_walls(const Polygon &plate, + double lo_z, + double hi_z, + ThrowOnCancel thr) +{ + return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); +} + +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void breakstick_holes(Points& pts, + double padding, + double stride, + double stick_width, + double penetration) +{ + if(stride <= EPSILON || stick_width <= EPSILON || padding <= EPSILON) + return; + + // SVG svg("bridgestick_plate.svg"); + // svg.draw(poly); + + // The connector stick will be a small rectangle with dimensions + // stick_width x (penetration + padding) to have some penetration + // into the input polygon. + + Points out; + out.reserve(2 * pts.size()); // output polygon points + + // stick bottom and right edge dimensions + double sbottom = scaled(stick_width); + double sright = scaled(penetration + padding); + + // scaled stride distance + double sstride = scaled(stride); + double t = 0; + + // process pairs of vertices as an edge, start with the last and + // first point + for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { + // Get vertices and the direction vectors + const Point &a = pts[i], &b = pts[j]; + Vec2d dir = b.cast() - a.cast(); + double nrm = dir.norm(); + dir /= nrm; + Vec2d dirp(-dir(Y), dir(X)); + + // Insert start point + out.emplace_back(a); + + // dodge the start point, do not make sticks on the joins + while (t < sbottom) t += sbottom; + double tend = nrm - sbottom; + + while (t < tend) { // insert the stick on the polygon perimeter + + // calculate the stick rectangle vertices and insert them + // into the output. + Point p1 = a + (t * dir).cast(); + Point p2 = p1 + (sright * dirp).cast(); + Point p3 = p2 + (sbottom * dir).cast(); + Point p4 = p3 + (sright * -dirp).cast(); + out.insert(out.end(), {p1, p2, p3, p4}); + + // continue along the perimeter + t += sstride; + } + + t = t - nrm; + + // Insert edge endpoint + out.emplace_back(b); + } + + // move the new points + out.shrink_to_fit(); + pts.swap(out); +} + +template +ExPolygons breakstick_holes(const ExPolygons &input, Args...args) +{ + ExPolygons ret = input; + for (ExPolygon &p : ret) { + breakstick_holes(p.contour.points, args...); + for (auto &h : p.holes) breakstick_holes(h.points, args...); + } + + return ret; +} + +static inline coord_t get_waffle_offset(const PadConfig &c) +{ + return scaled(c.brim_size_mm + c.wing_distance()); +} + +static inline double get_merge_distance(const PadConfig &c) +{ + return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm; +} + +// Part of the pad configuration that is used for 3D geometry generation +struct PadConfig3D { + double thickness, height, wing_height, slope; + + explicit PadConfig3D(const PadConfig &cfg2d) + : thickness{cfg2d.wall_thickness_mm} + , height{cfg2d.full_height()} + , wing_height{cfg2d.wall_height_mm} + , slope{cfg2d.wall_slope} + {} + + inline double bottom_offset() const + { + return (thickness + wing_height) / std::tan(slope); + } +}; + +// Outer part of the skeleton is used to generate the waffled edges of the pad. +// Inner parts will not be waffled or offsetted. Inner parts are only used if +// pad is generated around the object and correspond to holes and inner polygons +// in the model blueprint. +struct PadSkeleton { ExPolygons inner, outer; }; + +PadSkeleton divide_blueprint(const ExPolygons &bp) +{ + ClipperLib::PolyTree ptree = union_pt(bp); + + PadSkeleton ret; + ret.inner.reserve(size_t(ptree.Total())); + ret.outer.reserve(size_t(ptree.Total())); + + for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) { + ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour)); + for (ClipperLib::PolyTree::PolyNode *child : node->Childs) { + if (child->IsHole()) { + poly.holes.emplace_back( + ClipperPath_to_Slic3rPolygon(child->Contour)); + + traverse_pt_unordered(child->Childs, &ret.inner); + } + else traverse_pt_unordered(child, &ret.inner); + } + + ret.outer.emplace_back(poly); + } + + return ret; +} + +// A helper class for storing polygons and maintaining a spatial index of their +// bounding boxes. +class Intersector { + BoxIndex m_index; + ExPolygons m_polys; + +public: + + // Add a new polygon to the index + void add(const ExPolygon &ep) + { + m_polys.emplace_back(ep); + m_index.insert(BoundingBox{ep}, unsigned(m_index.size())); + } + + // Check an arbitrary polygon for intersection with the indexed polygons + bool intersects(const ExPolygon &poly) + { + // Create a suitable query bounding box. + auto bb = poly.contour.bounding_box(); + + std::vector qres = m_index.query(bb, BoxIndex::qtIntersects); + + // Now check intersections on the actual polygons (not just the boxes) + bool is_overlap = false; + auto qit = qres.begin(); + while (!is_overlap && qit != qres.end()) + is_overlap = is_overlap || poly.overlaps(m_polys[(qit++)->second]); + + return is_overlap; + } +}; + +// This dummy intersector to implement the "force pad everywhere" feature +struct DummyIntersector +{ + inline void add(const ExPolygon &) {} + inline bool intersects(const ExPolygon &) { return true; } +}; + +template +class _AroundPadSkeleton : public PadSkeleton +{ + // A spatial index used to be able to efficiently find intersections of + // support polygons with the model polygons. + _Intersector m_intersector; + +public: + _AroundPadSkeleton(const ExPolygons &support_blueprint, + const ExPolygons &model_blueprint, + const PadConfig & cfg, + ThrowOnCancel thr) + { + // We need to merge the support and the model contours in a special + // way in which the model contours have to be substracted from the + // support contours. The pad has to have a hole in which the model can + // fit perfectly (thus the substraction -- diff_ex). Also, the pad has + // to be eliminated from areas where there is no need for a pad, due + // to missing supports. + + add_supports_to_index(support_blueprint); + + auto model_bp_offs = + offset_ex(model_blueprint, + scaled(cfg.embed_object.object_gap_mm), + ClipperLib::jtMiter, 1); + + ExPolygons fullcvh = + wafflized_concave_hull(support_blueprint, model_bp_offs, cfg, thr); + + auto model_bp_sticks = + breakstick_holes(model_bp_offs, cfg.embed_object.object_gap_mm, + cfg.embed_object.stick_stride_mm, + cfg.embed_object.stick_width_mm, + cfg.embed_object.stick_penetration_mm); + + ExPolygons fullpad = diff_ex(fullcvh, model_bp_sticks); + + remove_redundant_parts(fullpad); + + PadSkeleton divided = divide_blueprint(fullpad); + outer = std::move(divided.outer); + inner = std::move(divided.inner); + } + +private: + + // Add the support blueprint to the search index to be queried later + void add_supports_to_index(const ExPolygons &supp_bp) + { + for (auto &ep : supp_bp) m_intersector.add(ep); + } + + // Create the wafflized pad around all object in the scene. This pad doesnt + // have any holes yet. + ExPolygons wafflized_concave_hull(const ExPolygons &supp_bp, + const ExPolygons &model_bp, + const PadConfig &cfg, + ThrowOnCancel thr) + { + auto allin = reserve_vector(supp_bp.size() + model_bp.size()); + + for (auto &ep : supp_bp) allin.emplace_back(ep.contour); + for (auto &ep : model_bp) allin.emplace_back(ep.contour); + + ConcaveHull cchull{allin, get_merge_distance(cfg), thr}; + return offset_waffle_style_ex(cchull, get_waffle_offset(cfg)); + } + + // To remove parts of the pad skeleton which do not host any supports + void remove_redundant_parts(ExPolygons &parts) + { + auto endit = std::remove_if(parts.begin(), parts.end(), + [this](const ExPolygon &p) { + return !m_intersector.intersects(p); + }); + + parts.erase(endit, parts.end()); + } +}; + +using AroundPadSkeleton = _AroundPadSkeleton; +using BrimPadSkeleton = _AroundPadSkeleton; + +class BelowPadSkeleton : public PadSkeleton +{ +public: + BelowPadSkeleton(const ExPolygons &support_blueprint, + const ExPolygons &model_blueprint, + const PadConfig & cfg, + ThrowOnCancel thr) + { + outer.reserve(support_blueprint.size() + model_blueprint.size()); + + for (auto &ep : support_blueprint) outer.emplace_back(ep.contour); + for (auto &ep : model_blueprint) outer.emplace_back(ep.contour); + + ConcaveHull ochull{outer, get_merge_distance(cfg), thr}; + + outer = offset_waffle_style_ex(ochull, get_waffle_offset(cfg)); + } +}; + +// Offset the contour only, leave the holes untouched +template +ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args) +{ + ExPolygons tmp = offset_ex(poly.contour, float(delta), args...); + + if (tmp.empty()) return {}; + + Polygons holes = poly.holes; + for (auto &h : holes) h.reverse(); + + tmp = diff_ex(to_polygons(tmp), holes); + + if (tmp.empty()) return {}; + + return tmp.front(); +} + +bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, + ThrowOnCancel thr) +{ + auto logerr = []{BOOST_LOG_TRIVIAL(error)<<"Could not create pad cavity";}; + + double wing_distance = cfg.wing_height / std::tan(cfg.slope); + coord_t delta_inner = -scaled(cfg.thickness + wing_distance); + coord_t delta_middle = -scaled(cfg.thickness); + ExPolygon inner_base = offset_contour_only(top_poly, delta_inner); + ExPolygon middle_base = offset_contour_only(top_poly, delta_middle); + + if (inner_base.empty() || middle_base.empty()) { logerr(); return false; } + + ExPolygons pdiff = diff_ex(top_poly, middle_base.contour); + + if (pdiff.size() != 1) { logerr(); return false; } + + top_poly = pdiff.front(); + + double z_min = -cfg.wing_height, z_max = 0; + double offset_difference = -wing_distance; + pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max, + offset_difference, thr)); + + pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP)); + + return true; +} + +Contour3D create_outer_pad_geometry(const ExPolygons & skeleton, + const PadConfig3D &cfg, + ThrowOnCancel thr) +{ + Contour3D ret; + + for (const ExPolygon &pad_part : skeleton) { + ExPolygon top_poly{pad_part}; + ExPolygon bottom_poly = + offset_contour_only(pad_part, -scaled(cfg.bottom_offset())); + + if (bottom_poly.empty()) continue; + + double z_min = -cfg.height, z_max = 0; + ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min, + cfg.bottom_offset(), thr)); + + if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr)) + z_max = -cfg.wing_height; + + for (auto &h : bottom_poly.holes) + ret.merge(straight_walls(h, z_max, z_min, thr)); + + ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN)); + ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP)); + } + + return ret; +} + +Contour3D create_inner_pad_geometry(const ExPolygons & skeleton, + const PadConfig3D &cfg, + ThrowOnCancel thr) +{ + Contour3D ret; + + double z_max = 0., z_min = -cfg.height; + for (const ExPolygon &pad_part : skeleton) { + ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr)); + + for (auto &h : pad_part.holes) + ret.merge(straight_walls(h, z_max, z_min, thr)); + + ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN)); + ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP)); + } + + return ret; +} + +Contour3D create_pad_geometry(const PadSkeleton &skelet, + const PadConfig & cfg, + ThrowOnCancel thr) +{ +#ifndef NDEBUG + SVG svg("pad_skeleton.svg"); + svg.draw(skelet.outer, "green"); + svg.draw(skelet.inner, "blue"); + svg.Close(); +#endif + + PadConfig3D cfg3d(cfg); + return create_outer_pad_geometry(skelet.outer, cfg3d, thr) + .merge(create_inner_pad_geometry(skelet.inner, cfg3d, thr)); +} + +Contour3D create_pad_geometry(const ExPolygons &supp_bp, + const ExPolygons &model_bp, + const PadConfig & cfg, + ThrowOnCancel thr) +{ + PadSkeleton skelet; + + if (cfg.embed_object.enabled) { + if (cfg.embed_object.everywhere) + skelet = BrimPadSkeleton(supp_bp, model_bp, cfg, thr); + else + skelet = AroundPadSkeleton(supp_bp, model_bp, cfg, thr); + } else + skelet = BelowPadSkeleton(supp_bp, model_bp, cfg, thr); + + return create_pad_geometry(skelet, cfg, thr); +} + +} // namespace + +void pad_blueprint(const TriangleMesh & mesh, + ExPolygons & output, + const std::vector &heights, + ThrowOnCancel thrfn) +{ + if (mesh.empty()) return; + TriangleMeshSlicer slicer(&mesh); + + auto out = reserve_vector(heights.size()); + slicer.slice(heights, 0.f, &out, thrfn); + + size_t count = 0; + for(auto& o : out) count += o.size(); + + // Unification is expensive, a simplify also speeds up the pad generation + auto tmp = reserve_vector(count); + for(ExPolygons& o : out) + for(ExPolygon& e : o) { + auto&& exss = e.simplify(scaled(0.1)); + for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); + } + + ExPolygons utmp = union_ex(tmp); + + for(auto& o : utmp) { + auto&& smp = o.simplify(scaled(0.1)); + output.insert(output.end(), smp.begin(), smp.end()); + } +} + +void pad_blueprint(const TriangleMesh &mesh, + ExPolygons & output, + float h, + float layerh, + ThrowOnCancel thrfn) +{ + float gnd = float(mesh.bounding_box().min(Z)); + + std::vector slicegrid = grid(gnd, gnd + h, layerh); + pad_blueprint(mesh, output, slicegrid, thrfn); +} + +void create_pad(const ExPolygons &sup_blueprint, + const ExPolygons &model_blueprint, + TriangleMesh & out, + const PadConfig & cfg, + ThrowOnCancel thr) +{ + Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr); + out.merge(mesh(std::move(t))); +} + +std::string PadConfig::validate() const +{ + static const double constexpr MIN_BRIM_SIZE_MM = .1; + + if (brim_size_mm < MIN_BRIM_SIZE_MM || + bottom_offset() > brim_size_mm + wing_distance() || + get_waffle_offset(*this) <= MIN_BRIM_SIZE_MM) + return L("Pad brim size is too small for the current configuration."); + + return ""; +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SLAPad.hpp b/src/libslic3r/SLA/SLAPad.hpp new file mode 100644 index 0000000000..4abcdd281e --- /dev/null +++ b/src/libslic3r/SLA/SLAPad.hpp @@ -0,0 +1,94 @@ +#ifndef SLABASEPOOL_HPP +#define SLABASEPOOL_HPP + +#include +#include +#include +#include + +namespace Slic3r { + +class ExPolygon; +class Polygon; +using ExPolygons = std::vector; +using Polygons = std::vector; + +class TriangleMesh; + +namespace sla { + +using ThrowOnCancel = std::function; + +/// Calculate the polygon representing the silhouette. +void pad_blueprint( + const TriangleMesh &mesh, // input mesh + ExPolygons & output, // Output will be merged with + const std::vector &, // Exact Z levels to sample + ThrowOnCancel thrfn = [] {}); // Function that throws if cancel was requested + +void pad_blueprint( + const TriangleMesh &mesh, + ExPolygons & output, + float samplingheight = 0.1f, // The height range to sample + float layerheight = 0.05f, // The sampling height + ThrowOnCancel thrfn = [] {}); + +struct PadConfig { + double wall_thickness_mm = 1.; + double wall_height_mm = 1.; + double max_merge_dist_mm = 50; + double wall_slope = std::atan(1.0); // Universal constant for Pi/4 + double brim_size_mm = 1.6; + + struct EmbedObject { + double object_gap_mm = 1.; + double stick_stride_mm = 10.; + double stick_width_mm = 0.5; + double stick_penetration_mm = 0.1; + bool enabled = false; + bool everywhere = false; + operator bool() const { return enabled; } + } embed_object; + + inline PadConfig() = default; + inline PadConfig(double thickness, + double height, + double mergedist, + double slope) + : wall_thickness_mm(thickness) + , wall_height_mm(height) + , max_merge_dist_mm(mergedist) + , wall_slope(slope) + {} + + inline double bottom_offset() const + { + return (wall_thickness_mm + wall_height_mm) / std::tan(wall_slope); + } + + inline double wing_distance() const + { + return wall_height_mm / std::tan(wall_slope); + } + + inline double full_height() const + { + return wall_height_mm + wall_thickness_mm; + } + + /// Returns the elevation needed for compensating the pad. + inline double required_elevation() const { return wall_thickness_mm; } + + std::string validate() const; +}; + +void create_pad(const ExPolygons &support_contours, + const ExPolygons &model_contours, + TriangleMesh & output_mesh, + const PadConfig & = PadConfig(), + ThrowOnCancel throw_on_cancel = []{}); + +} // namespace sla +} // namespace Slic3r + +#endif // SLABASEPOOL_HPP diff --git a/src/libslic3r/SLA/SLARaster.cpp b/src/libslic3r/SLA/SLARaster.cpp index 32a88b1b50..091cadd231 100644 --- a/src/libslic3r/SLA/SLARaster.cpp +++ b/src/libslic3r/SLA/SLARaster.cpp @@ -5,6 +5,7 @@ #include "SLARaster.hpp" #include "libslic3r/ExPolygon.hpp" +#include "libslic3r/MTUtils.hpp" #include // For rasterizing @@ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H namespace sla { +const Raster::TMirroring Raster::NoMirror = {false, false}; +const Raster::TMirroring Raster::MirrorX = {true, false}; +const Raster::TMirroring Raster::MirrorY = {false, true}; +const Raster::TMirroring Raster::MirrorXY = {true, true}; + + +using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24; +using TRawRenderer = agg::renderer_base; +using TPixel = TPixelRenderer::color_type; +using TRawBuffer = agg::rendering_buffer; +using TBuffer = std::vector; + +using TRendererAA = agg::renderer_scanline_aa_solid; + class Raster::Impl { public: - using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24; - using TRawRenderer = agg::renderer_base; - using TPixel = TPixelRenderer::color_type; - using TRawBuffer = agg::rendering_buffer; - - using TBuffer = std::vector; - - using TRendererAA = agg::renderer_scanline_aa_solid; static const TPixel ColorWhite; static const TPixel ColorBlack; - using Format = Raster::Format; + using Format = Raster::RawData; private: Raster::Resolution m_resolution; -// Raster::PixelDim m_pxdim; Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons TBuffer m_buf; TRawBuffer m_rbuf; @@ -59,74 +65,49 @@ private: TRendererAA m_renderer; std::function m_gammafn; - std::array m_mirror; - Format m_fmt = Format::PNG; + Trafo m_trafo; inline void flipy(agg::path_storage& path) const { - path.flip_y(0, m_resolution.height_px); + path.flip_y(0, double(m_resolution.height_px)); } inline void flipx(agg::path_storage& path) const { - path.flip_x(0, m_resolution.width_px); + path.flip_x(0, double(m_resolution.width_px)); } public: - - inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, - const std::array& mirror, double gamma = 1.0): - m_resolution(res), -// m_pxdim(pd), - m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm), - m_buf(res.pixels()), - m_rbuf(reinterpret_cast(m_buf.data()), - res.width_px, res.height_px, - int(res.width_px*TPixelRenderer::num_components)), - m_pixfmt(m_rbuf), - m_raw_renderer(m_pixfmt), - m_renderer(m_raw_renderer), - m_mirror(mirror) + inline Impl(const Raster::Resolution & res, + const Raster::PixelDim & pd, + const Trafo &trafo) + : m_resolution(res) + , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) + , m_buf(res.pixels()) + , m_rbuf(reinterpret_cast(m_buf.data()), + unsigned(res.width_px), + unsigned(res.height_px), + int(res.width_px * TPixelRenderer::num_components)) + , m_pixfmt(m_rbuf) + , m_raw_renderer(m_pixfmt) + , m_renderer(m_raw_renderer) + , m_trafo(trafo) { m_renderer.color(ColorWhite); - if(gamma > 0) m_gammafn = agg::gamma_power(gamma); + if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma); else m_gammafn = agg::gamma_threshold(0.5); clear(); } - - inline Impl(const Raster::Resolution& res, - const Raster::PixelDim &pd, - Format fmt, - double gamma = 1.0): - Impl(res, pd, {false, false}, gamma) - { - switch (fmt) { - case Format::PNG: m_mirror = {false, true}; break; - case Format::RAW: m_mirror = {false, false}; break; - } - m_fmt = fmt; - } template void draw(const P &poly) { agg::rasterizer_scanline_aa<> ras; agg::scanline_p8 scanlines; ras.gamma(m_gammafn); - - auto&& path = to_path(contour(poly)); - if(m_mirror[X]) flipx(path); - if(m_mirror[Y]) flipy(path); - - ras.add_path(path); - - for(auto& h : holes(poly)) { - auto&& holepath = to_path(h); - if(m_mirror[X]) flipx(holepath); - if(m_mirror[Y]) flipy(holepath); - ras.add_path(holepath); - } - + ras.add_path(to_path(contour(poly))); + for(auto& h : holes(poly)) ras.add_path(to_path(h)); + agg::render_scanlines(ras, scanlines, m_renderer); } @@ -135,11 +116,16 @@ public: } inline TBuffer& buffer() { return m_buf; } + inline const TBuffer& buffer() const { return m_buf; } - inline Format format() const { return m_fmt; } inline const Raster::Resolution resolution() { return m_resolution; } - + inline const Raster::PixelDim pixdim() + { + return {SCALING_FACTOR / m_pxdim_scaled.w_mm, + SCALING_FACTOR / m_pxdim_scaled.h_mm}; + } + private: inline double getPx(const Point& p) { return p(0) * m_pxdim_scaled.w_mm; @@ -162,49 +148,67 @@ private: return p.Y * m_pxdim_scaled.h_mm; } - template agg::path_storage to_path(const PointVec& poly) + template agg::path_storage _to_path(const PointVec& v) { agg::path_storage path; - auto it = poly.begin(); + auto it = v.begin(); path.move_to(getPx(*it), getPy(*it)); + while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); + path.line_to(getPx(v.front()), getPy(v.front())); + + return path; + } + + template agg::path_storage _to_path_flpxy(const PointVec& v) + { + agg::path_storage path; + + auto it = v.begin(); + path.move_to(getPy(*it), getPx(*it)); + while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); + path.line_to(getPy(v.front()), getPx(v.front())); + + return path; + } + + template agg::path_storage to_path(const PointVec &v) + { + auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); + + path.translate_all_paths(m_trafo.origin_x * m_pxdim_scaled.w_mm, + m_trafo.origin_y * m_pxdim_scaled.h_mm); + + if(m_trafo.mirror_x) flipx(path); + if(m_trafo.mirror_y) flipy(path); - while(++it != poly.end()) - path.line_to(getPx(*it), getPy(*it)); - - path.line_to(getPx(poly.front()), getPy(poly.front())); return path; } }; -const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); -const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); +const TPixel Raster::Impl::ColorWhite = TPixel(255); +const TPixel Raster::Impl::ColorBlack = TPixel(0); + +Raster::Raster() { reset(); } + +Raster::Raster(const Raster::Resolution &r, + const Raster::PixelDim & pd, + const Raster::Trafo & tr) +{ + reset(r, pd, tr); +} -template<> Raster::Raster() { reset(); }; Raster::~Raster() = default; -// Raster::Raster(Raster &&m) = default; -// Raster& Raster::operator=(Raster&&) = default; - -// FIXME: remove after migrating to higher version of windows compiler -Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {} -Raster& Raster::operator=(Raster &&m) { - m_impl = std::move(m.m_impl); return *this; -} +Raster::Raster(Raster &&m) = default; +Raster &Raster::operator=(Raster &&) = default; void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - Format fmt, double gamma) + const Trafo &trafo) { m_impl.reset(); - m_impl.reset(new Impl(r, pd, fmt, gamma)); -} - -void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - const std::array& mirror, double gamma) -{ - m_impl.reset(); - m_impl.reset(new Impl(r, pd, mirror, gamma)); + m_impl.reset(new Impl(r, pd, trafo)); } void Raster::reset() @@ -214,9 +218,16 @@ void Raster::reset() Raster::Resolution Raster::resolution() const { - if(m_impl) return m_impl->resolution(); + if (m_impl) return m_impl->resolution(); + + return Resolution{0, 0}; +} - return Resolution(0, 0); +Raster::PixelDim Raster::pixel_dimensions() const +{ + if (m_impl) return m_impl->pixdim(); + + return PixelDim{0., 0.}; } void Raster::clear() @@ -227,103 +238,83 @@ void Raster::clear() void Raster::draw(const ExPolygon &expoly) { + assert(m_impl); m_impl->draw(expoly); } void Raster::draw(const ClipperLib::Polygon &poly) { + assert(m_impl); m_impl->draw(poly); } -void Raster::save(std::ostream& stream, Format fmt) +uint8_t Raster::read_pixel(size_t x, size_t y) const { - assert(m_impl); - if(!stream.good()) return; - - switch(fmt) { - case Format::PNG: { - auto& b = m_impl->buffer(); - size_t out_len = 0; - void * rawdata = tdefl_write_image_to_png_file_in_memory( - b.data(), - int(resolution().width_px), - int(resolution().height_px), 1, &out_len); - - if(rawdata == nullptr) break; - - stream.write(static_cast(rawdata), - std::streamsize(out_len)); - - MZ_FREE(rawdata); - - break; - } - case Format::RAW: { - stream << "P5 " - << m_impl->resolution().width_px << " " - << m_impl->resolution().height_px << " " - << "255 "; - - auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type); - stream.write(reinterpret_cast(m_impl->buffer().data()), - std::streamsize(sz)); - } - } + assert (m_impl); + TPixel::value_type px; + m_impl->buffer()[y * resolution().width_px + x].get(px); + return px; } -void Raster::save(std::ostream &stream) +PNGImage & PNGImage::serialize(const Raster &raster) { - save(stream, m_impl->format()); + size_t s = 0; + m_buffer.clear(); + + void *rawdata = tdefl_write_image_to_png_file_in_memory( + get_internals(raster).buffer().data(), + int(raster.resolution().width_px), + int(raster.resolution().height_px), 1, &s); + + // On error, data() will return an empty vector. No other info can be + // retrieved from miniz anyway... + if (rawdata == nullptr) return *this; + + auto ptr = static_cast(rawdata); + + m_buffer.reserve(s); + std::copy(ptr, ptr + s, std::back_inserter(m_buffer)); + + MZ_FREE(rawdata); + return *this; } -RawBytes Raster::save(Format fmt) +std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes) { - assert(m_impl); - - std::vector data; size_t s = 0; - - switch(fmt) { - case Format::PNG: { - void *rawdata = tdefl_write_image_to_png_file_in_memory( - m_impl->buffer().data(), - int(resolution().width_px), - int(resolution().height_px), 1, &s); - - if(rawdata == nullptr) break; - auto ptr = static_cast(rawdata); - - data.reserve(s); std::copy(ptr, ptr + s, std::back_inserter(data)); - - MZ_FREE(rawdata); - break; - } - case Format::RAW: { - auto header = std::string("P5 ") + - std::to_string(m_impl->resolution().width_px) + " " + - std::to_string(m_impl->resolution().height_px) + " " + "255 "; - - auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type); - s = sz + header.size(); - - data.reserve(s); - - auto buff = reinterpret_cast(m_impl->buffer().data()); - std::copy(header.begin(), header.end(), std::back_inserter(data)); - std::copy(buff, buff+sz, std::back_inserter(data)); - - break; - } - } - - return {std::move(data)}; + stream.write(reinterpret_cast(bytes.data()), + std::streamsize(bytes.size())); + + return stream; } -RawBytes Raster::save() +Raster::RawData::~RawData() = default; + +PPMImage & PPMImage::serialize(const Raster &raster) { - return save(m_impl->format()); + auto header = std::string("P5 ") + + std::to_string(raster.resolution().width_px) + " " + + std::to_string(raster.resolution().height_px) + " " + "255 "; + + const auto &impl = get_internals(raster); + auto sz = impl.buffer().size() * sizeof(TBuffer::value_type); + size_t s = sz + header.size(); + + m_buffer.clear(); + m_buffer.reserve(s); + + auto buff = reinterpret_cast(impl.buffer().data()); + std::copy(header.begin(), header.end(), std::back_inserter(m_buffer)); + std::copy(buff, buff+sz, std::back_inserter(m_buffer)); + + return *this; } +const Raster::Impl &Raster::RawData::get_internals(const Raster &raster) +{ + return *raster.m_impl; } -} + +} // namespace sla +} // namespace Slic3r #endif // SLARASTER_CPP diff --git a/src/libslic3r/SLA/SLARaster.hpp b/src/libslic3r/SLA/SLARaster.hpp index 8b27fd1533..b3d73536bb 100644 --- a/src/libslic3r/SLA/SLARaster.hpp +++ b/src/libslic3r/SLA/SLARaster.hpp @@ -8,45 +8,13 @@ #include #include +#include + namespace ClipperLib { struct Polygon; } -namespace Slic3r { - -class ExPolygon; - +namespace Slic3r { namespace sla { -// Raw byte buffer paired with its size. Suitable for compressed PNG data. -class RawBytes { - - std::vector m_buffer; -public: - - RawBytes() = default; - RawBytes(std::vector&& data): m_buffer(std::move(data)) {} - - size_t size() const { return m_buffer.size(); } - const uint8_t * data() { return m_buffer.data(); } - - RawBytes(const RawBytes&) = delete; - RawBytes& operator=(const RawBytes&) = delete; - - // ///////////////////////////////////////////////////////////////////////// - // FIXME: the following is needed for MSVC2013 compatibility - // ///////////////////////////////////////////////////////////////////////// - - // RawBytes(RawBytes&&) = default; - // RawBytes& operator=(RawBytes&&) = default; - - RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} - RawBytes& operator=(RawBytes&& mv) { - m_buffer = std::move(mv.m_buffer); - return *this; - } - - // ///////////////////////////////////////////////////////////////////////// -}; - /** * @brief Raster captures an anti-aliased monochrome canvas where vectorial * polygons can be rasterized. Fill color is always white and the background is @@ -60,10 +28,28 @@ class Raster { std::unique_ptr m_impl; public: - /// Supported compression types - enum class Format { - RAW, //!> Uncompressed pixel data - PNG //!> PNG compression + // Raw byte buffer paired with its size. Suitable for compressed image data. + class RawData + { + protected: + std::vector m_buffer; + const Impl& get_internals(const Raster& raster); + public: + RawData() = default; + RawData(std::vector&& data): m_buffer(std::move(data)) {} + virtual ~RawData(); + + RawData(const RawData &) = delete; + RawData &operator=(const RawData &) = delete; + + RawData(RawData &&) = default; + RawData &operator=(RawData &&) = default; + + size_t size() const { return m_buffer.size(); } + const uint8_t * data() const { return m_buffer.data(); } + + virtual RawData& serialize(const Raster &/*raster*/) { return *this; } + virtual std::string get_file_extension() const = 0; }; /// Type that represents a resolution in pixels. @@ -86,11 +72,36 @@ public: w_mm(px_width_mm), h_mm(px_height_mm) {} }; - /// Constructor taking the resolution and the pixel dimension. - template Raster(Args...args) { - reset(std::forward(args)...); - } - + enum Orientation { roLandscape, roPortrait }; + + using TMirroring = std::array; + static const TMirroring NoMirror; + static const TMirroring MirrorX; + static const TMirroring MirrorY; + static const TMirroring MirrorXY; + + struct Trafo { + bool mirror_x = false, mirror_y = false, flipXY = false; + coord_t origin_x = 0, origin_y = 0; + + // If gamma is zero, thresholding will be performed which disables AA. + double gamma = 1.; + + // Portrait orientation will make sure the drawed polygons are rotated + // by 90 degrees. + Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) + // XY flipping implicitly does an X mirror + : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) + , mirror_y(!mirror[1]) // Makes raster origin to be top left corner + , flipXY(o == roPortrait) + {} + }; + + Raster(); + Raster(const Resolution &r, + const PixelDim & pd, + const Trafo & tr = {}); + Raster(const Raster& cpy) = delete; Raster& operator=(const Raster& cpy) = delete; Raster(Raster&& m); @@ -98,18 +109,10 @@ public: ~Raster(); /// Reallocated everything for the given resolution and pixel dimension. - /// The third parameter is either the X, Y mirroring or a supported format - /// for which the correct mirroring will be configured. - void reset(const Resolution&, - const PixelDim&, - const std::array& mirror, - double gamma = 1.0); - - void reset(const Resolution& r, - const PixelDim& pd, - Format o, - double gamma = 1.0); - + void reset(const Resolution& r, + const PixelDim& pd, + const Trafo &tr = {}); + /** * Release the allocated resources. Drawing in this state ends in * unspecified behavior. @@ -118,6 +121,7 @@ public: /// Get the resolution of the raster. Resolution resolution() const; + PixelDim pixel_dimensions() const; /// Clear the raster with black color. void clear(); @@ -126,24 +130,28 @@ public: void draw(const ExPolygon& poly); void draw(const ClipperLib::Polygon& poly); - // Saving the raster: - // It is possible to override the format given in the constructor but - // be aware that the mirroring will not be modified. - - /// Save the raster on the specified stream. - void save(std::ostream& stream, Format); - void save(std::ostream& stream); + uint8_t read_pixel(size_t w, size_t h) const; + + inline bool empty() const { return ! bool(m_impl); } - /// Save into a continuous byte stream which is returned. - RawBytes save(Format fmt); - RawBytes save(); }; -// This prevents the duplicate default constructor warning on MSVC2013 -template<> Raster::Raster(); +class PNGImage: public Raster::RawData { +public: + PNGImage& serialize(const Raster &raster) override; + std::string get_file_extension() const override { return "png"; } +}; +class PPMImage: public Raster::RawData { +public: + PPMImage& serialize(const Raster &raster) override; + std::string get_file_extension() const override { return "ppm"; } +}; + +std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes); } // sla } // Slic3r + #endif // SLARASTER_HPP diff --git a/src/libslic3r/SLA/SLARasterWriter.cpp b/src/libslic3r/SLA/SLARasterWriter.cpp index 3e6f015d4f..6ac86827ef 100644 --- a/src/libslic3r/SLA/SLARasterWriter.cpp +++ b/src/libslic3r/SLA/SLARasterWriter.cpp @@ -10,7 +10,7 @@ namespace Slic3r { namespace sla { -std::string SLARasterWriter::createIniContent(const std::string& projectname) const +std::string RasterWriter::createIniContent(const std::string& projectname) const { std::string out("action = print\njobDir = "); out += projectname + "\n"; @@ -21,65 +21,51 @@ std::string SLARasterWriter::createIniContent(const std::string& projectname) co return out; } -void SLARasterWriter::flpXY(ClipperLib::Polygon &poly) -{ - for(auto& p : poly.Contour) std::swap(p.X, p.Y); - std::reverse(poly.Contour.begin(), poly.Contour.end()); - - for(auto& h : poly.Holes) { - for(auto& p : h) std::swap(p.X, p.Y); - std::reverse(h.begin(), h.end()); - } -} +RasterWriter::RasterWriter(const Raster::Resolution &res, + const Raster::PixelDim & pixdim, + const Raster::Trafo & trafo, + double gamma) + : m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma) +{} -void SLARasterWriter::flpXY(ExPolygon &poly) -{ - for(auto& p : poly.contour.points) p = Point(p.y(), p.x()); - std::reverse(poly.contour.points.begin(), poly.contour.points.end()); - - for(auto& h : poly.holes) { - for(auto& p : h.points) p = Point(p.y(), p.x()); - std::reverse(h.points.begin(), h.points.end()); - } -} - -SLARasterWriter::SLARasterWriter(const Raster::Resolution &res, - const Raster::PixelDim &pixdim, - const std::array &mirror, - double gamma) - : m_res(res), m_pxdim(pixdim), m_mirror(mirror), m_gamma(gamma) -{ - // PNG raster will implicitly do an Y mirror - m_mirror[1] = !m_mirror[1]; -} - -void SLARasterWriter::save(const std::string &fpath, const std::string &prjname) +void RasterWriter::save(const std::string &fpath, const std::string &prjname) { try { Zipper zipper(fpath); // zipper with no compression - - std::string project = prjname.empty()? - boost::filesystem::path(fpath).stem().string() : prjname; - + save(zipper, prjname); + zipper.finalize(); + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } +} + +void RasterWriter::save(Zipper &zipper, const std::string &prjname) +{ + try { + std::string project = + prjname.empty() ? + boost::filesystem::path(zipper.get_filename()).stem().string() : + prjname; + zipper.add_entry("config.ini"); - + zipper << createIniContent(project); - + for(unsigned i = 0; i < m_layers_rst.size(); i++) { if(m_layers_rst[i].rawbytes.size() > 0) { char lyrnum[6]; std::sprintf(lyrnum, "%.5d", i); auto zfilename = project + lyrnum + ".png"; - + // Add binary entry to the zipper zipper.add_entry(zfilename, m_layers_rst[i].rawbytes.data(), m_layers_rst[i].rawbytes.size()); } } - - zipper.finalize(); } catch(std::exception& e) { BOOST_LOG_TRIVIAL(error) << e.what(); // Rethrow the exception @@ -103,7 +89,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) } // namespace -void SLARasterWriter::set_config(const DynamicPrintConfig &cfg) +void RasterWriter::set_config(const DynamicPrintConfig &cfg) { m_config["layerHeight"] = get_cfg_value(cfg, "layer_height"); m_config["expTime"] = get_cfg_value(cfg, "exposure_time"); @@ -114,11 +100,11 @@ void SLARasterWriter::set_config(const DynamicPrintConfig &cfg) m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); - m_config["fileCreationTimestamp"] = Utils::current_utc_time2str(); + m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID; } -void SLARasterWriter::set_statistics(const PrintStatistics &stats) +void RasterWriter::set_statistics(const PrintStatistics &stats) { m_config["usedMaterial"] = std::to_string(stats.used_material); m_config["numFade"] = std::to_string(stats.num_fade); diff --git a/src/libslic3r/SLA/SLARasterWriter.hpp b/src/libslic3r/SLA/SLARasterWriter.hpp index b9202c464f..93a315c821 100644 --- a/src/libslic3r/SLA/SLARasterWriter.hpp +++ b/src/libslic3r/SLA/SLARasterWriter.hpp @@ -12,23 +12,21 @@ #include "libslic3r/PrintConfig.hpp" #include "SLARaster.hpp" +#include "libslic3r/Zipper.hpp" namespace Slic3r { namespace sla { -// Implementation for PNG raster output +// API to write the zipped sla output layers and metadata. +// Implementation uses PNG raster output. // Be aware that if a large number of layers are allocated, it can very well // exhaust the available memory especially on 32 bit platform. // This class is designed to be used in parallel mode. Layers have an ID and // each layer can be written and compressed independently (in parallel). // At the end when all layers where written, the save method can be used to // write out the result into a zipped archive. -class SLARasterWriter +class RasterWriter { public: - enum Orientation { - roLandscape, - roPortrait - }; // Used for addressing parameters of set_statistics() struct PrintStatistics @@ -45,7 +43,7 @@ private: // A struct to bind the raster image data and its compressed bytes together. struct Layer { Raster raster; - RawBytes rawbytes; + PNGImage rawbytes; Layer() = default; @@ -61,78 +59,64 @@ private: // parallel. Later we can write every layer to the disk sequentially. std::vector m_layers_rst; Raster::Resolution m_res; - Raster::PixelDim m_pxdim; - std::array m_mirror; - double m_gamma; - + Raster::PixelDim m_pxdim; + Raster::Trafo m_trafo; + double m_gamma; + std::map m_config; std::string createIniContent(const std::string& projectname) const; - - static void flpXY(ClipperLib::Polygon& poly); - static void flpXY(ExPolygon& poly); public: - SLARasterWriter(const Raster::Resolution &res, - const Raster::PixelDim &pixdim, - const std::array &mirror, - double gamma = 1.); + + // SLARasterWriter is using Raster in custom mirroring mode + RasterWriter(const Raster::Resolution &res, + const Raster::PixelDim & pixdim, + const Raster::Trafo & trafo, + double gamma = 1.); - SLARasterWriter(const SLARasterWriter& ) = delete; - SLARasterWriter& operator=(const SLARasterWriter&) = delete; - SLARasterWriter(SLARasterWriter&& m) = default; - SLARasterWriter& operator=(SLARasterWriter&&) = default; + RasterWriter(const RasterWriter& ) = delete; + RasterWriter& operator=(const RasterWriter&) = delete; + RasterWriter(RasterWriter&& m) = default; + RasterWriter& operator=(RasterWriter&&) = default; inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - template void draw_polygon(const Poly& p, unsigned lyr, - Orientation o = roPortrait) + template void draw_polygon(const Poly& p, unsigned lyr) { assert(lyr < m_layers_rst.size()); - - switch (o) { - case roPortrait: { - Poly poly(p); - flpXY(poly); - m_layers_rst[lyr].raster.draw(poly); - break; - } - case roLandscape: - m_layers_rst[lyr].raster.draw(p); - break; - } + m_layers_rst[lyr].raster.draw(p); } inline void begin_layer(unsigned lyr) { if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma); + m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo); } inline void begin_layer() { m_layers_rst.emplace_back(); - m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma); + m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo); } inline void finish_layer(unsigned lyr_id) { assert(lyr_id < m_layers_rst.size()); - m_layers_rst[lyr_id].rawbytes = - m_layers_rst[lyr_id].raster.save(Raster::Format::PNG); + m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster); m_layers_rst[lyr_id].raster.reset(); } inline void finish_layer() { if(!m_layers_rst.empty()) { - m_layers_rst.back().rawbytes = - m_layers_rst.back().raster.save(Raster::Format::PNG); + m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster); m_layers_rst.back().raster.reset(); } } void save(const std::string &fpath, const std::string &prjname = ""); + void save(Zipper &zipper, const std::string &prjname = ""); void set_statistics(const PrintStatistics &statistics); - + void set_config(const DynamicPrintConfig &cfg); }; diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index 90dcdc3627..20b6fcd589 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -39,14 +39,19 @@ public: insert(std::make_pair(v, unsigned(idx))); } - std::vector query(std::function); - std::vector nearest(const Vec3d&, unsigned k); + std::vector query(std::function) const; + std::vector nearest(const Vec3d&, unsigned k) const; + std::vector query(const Vec3d &v, unsigned k) const // wrapper + { + return nearest(v, k); + } // For testing size_t size() const; bool empty() const { return size() == 0; } void foreach(std::function fn); + void foreach(std::function fn) const; }; using BoxIndexEl = std::pair; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 99f7bc8b3a..fea8bf731c 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -7,7 +7,7 @@ #include "SLASupportTree.hpp" #include "SLABoilerPlate.hpp" #include "SLASpatIndex.hpp" -#include "SLABasePool.hpp" +#include "SLASupportTreeBuilder.hpp" #include #include @@ -17,51 +17,14 @@ #include #include #include +#include +#include #include //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) -/** - * Terminology: - * - * Support point: - * The point on the model surface that needs support. - * - * Pillar: - * A thick column that spans from a support point to the ground and has - * a thick cone shaped base where it touches the ground. - * - * Ground facing support point: - * A support point that can be directly connected with the ground with a pillar - * that does not collide or cut through the model. - * - * Non ground facing support point: - * A support point that cannot be directly connected with the ground (only with - * the model surface). - * - * Head: - * The pinhead that connects to the model surface with the sharp end end - * to a pillar or bridge stick with the dull end. - * - * Headless support point: - * A support point on the model surface for which there is not enough place for - * the head. It is either in a hole or there is some barrier that would collide - * with the head geometry. The headless support point can be ground facing and - * non ground facing as well. - * - * Bridge: - * A stick that connects two pillars or a head with a pillar. - * - * Junction: - * A small ball in the intersection of two or more sticks (pillar, bridge, ...) - * - * CompactBridge: - * A bridge that connects a headless support point with the model surface or a - * nearby pillar. - */ - namespace Slic3r { namespace sla { @@ -80,2527 +43,16 @@ const unsigned SupportConfig::optimizer_max_iterations = 1000; const unsigned SupportConfig::pillar_cascade_neighbors = 3; const unsigned SupportConfig::max_bridges_on_pillar = 3; -using Coordf = double; -using Portion = std::tuple; - -// Set this to true to enable full parallelism in this module. -// Only the well tested parts will be concurrent if this is set to false. -const constexpr bool USE_FULL_CONCURRENCY = false; - -template struct _ccr {}; - -template<> struct _ccr -{ - using Mutex = SpinMutex; - - template - static inline void enumerate(It from, It to, Fn fn) - { - using TN = size_t; - auto iN = to - from; - TN N = iN < 0 ? 0 : TN(iN); - - tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); }); - } -}; - -template<> struct _ccr -{ - struct Mutex { inline void lock() {} inline void unlock() {} }; - - template - static inline void enumerate(It from, It to, Fn fn) - { - for (auto it = from; it != to; ++it) fn(*it, it - from); - } -}; - -using ccr = _ccr; -using ccr_seq = _ccr; -using ccr_par = _ccr; - -inline Portion make_portion(double a, double b) { - return std::make_tuple(a, b); +void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const { + outmesh.merge(retrieve_mesh(MeshType::Support)); + outmesh.merge(retrieve_mesh(MeshType::Pad)); } -template double distance(const Vec& p) { - return std::sqrt(p.transpose() * p); -} - -template double distance(const Vec& pp1, const Vec& pp2) { - auto p = pp2 - pp1; - return distance(p); -} - -Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), - double fa=(2*PI/360)) { - - Contour3D ret; - - // prohibit close to zero radius - if(rho <= 1e-6 && rho >= -1e-6) return ret; - - auto& vertices = ret.points; - auto& facets = ret.indices; - - // Algorithm: - // Add points one-by-one to the sphere grid and form facets using relative - // coordinates. Sphere is composed effectively of a mesh of stacked circles. - - // adjust via rounding to get an even multiple for any provided angle. - double angle = (2*PI / floor(2*PI / fa)); - - // Ring to be scaled to generate the steps of the sphere - std::vector ring; - - for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); - - const auto sbegin = size_t(2*std::get<0>(portion)/angle); - const auto send = size_t(2*std::get<1>(portion)/angle); - - const size_t steps = ring.size(); - const double increment = 1.0 / double(steps); - - // special case: first ring connects to 0,0,0 - // insert and form facets. - if(sbegin == 0) - vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); - - auto id = coord_t(vertices.size()); - for (size_t i = 0; i < ring.size(); i++) { - // Fixed scaling - const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); - // radius of the circle for this step. - const double r = std::sqrt(std::abs(rho*rho - z*z)); - Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(b(0), b(1), z)); - - if(sbegin == 0) - facets.emplace_back((i == 0) ? Vec3crd(coord_t(ring.size()), 0, 1) : - Vec3crd(id - 1, 0, id)); - ++ id; - } - - // General case: insert and form facets for each step, - // joining it to the ring below it. - for (size_t s = sbegin + 2; s < send - 1; s++) { - const double z = -rho + increment*double(s*2.0*rho); - const double r = std::sqrt(std::abs(rho*rho - z*z)); - - for (size_t i = 0; i < ring.size(); i++) { - Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(b(0), b(1), z)); - auto id_ringsize = coord_t(id - int(ring.size())); - if (i == 0) { - // wrap around - facets.emplace_back(Vec3crd(id - 1, id, - id + coord_t(ring.size() - 1))); - facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); - } else { - facets.emplace_back(Vec3crd(id_ringsize - 1, id_ringsize, id)); - facets.emplace_back(Vec3crd(id - 1, id_ringsize - 1, id)); - } - id++; - } - } - - // special case: last ring connects to 0,0,rho*2.0 - // only form facets. - if(send >= size_t(2*PI / angle)) { - vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); - for (size_t i = 0; i < ring.size(); i++) { - auto id_ringsize = coord_t(id - int(ring.size())); - if (i == 0) { - // third vertex is on the other side of the ring. - facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); - } else { - auto ci = coord_t(id_ringsize + coord_t(i)); - facets.emplace_back(Vec3crd(ci - 1, ci, id)); - } - } - } - id++; - - return ret; -} - -// Down facing cylinder in Z direction with arguments: -// r: radius -// h: Height -// ssteps: how many edges will create the base circle -// sp: starting point -Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0}) -{ - Contour3D ret; - - auto steps = int(ssteps); - auto& points = ret.points; - auto& indices = ret.indices; - points.reserve(2*ssteps); - double a = 2*PI/steps; - - Vec3d jp = sp; - Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; - - // Upper circle points - for(int i = 0; i < steps; ++i) { - double phi = i*a; - double ex = endp(X) + r*std::cos(phi); - double ey = endp(Y) + r*std::sin(phi); - points.emplace_back(ex, ey, endp(Z)); - } - - // Lower circle points - for(int i = 0; i < steps; ++i) { - double phi = i*a; - double x = jp(X) + r*std::cos(phi); - double y = jp(Y) + r*std::sin(phi); - points.emplace_back(x, y, jp(Z)); - } - - // Now create long triangles connecting upper and lower circles - indices.reserve(2*ssteps); - auto offs = steps; - for(int i = 0; i < steps - 1; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - } - - // Last triangle connecting the first and last vertices - auto last = steps - 1; - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - - // According to the slicing algorithms, we need to aid them with generating - // a watertight body. So we create a triangle fan for the upper and lower - // ending of the cylinder to close the geometry. - points.emplace_back(jp); int ci = int(points.size() - 1); - for(int i = 0; i < steps - 1; ++i) - indices.emplace_back(i + offs + 1, i + offs, ci); - - indices.emplace_back(offs, steps + offs - 1, ci); - - points.emplace_back(endp); ci = int(points.size() - 1); - for(int i = 0; i < steps - 1; ++i) - indices.emplace_back(ci, i, i + 1); - - indices.emplace_back(steps - 1, 0, ci); - - return ret; -} - -struct Head { - Contour3D mesh; - - size_t steps = 45; - Vec3d dir = {0, 0, -1}; - Vec3d tr = {0, 0, 0}; - - double r_back_mm = 1; - double r_pin_mm = 0.5; - double width_mm = 2; - double penetration_mm = 0.5; - - // For identification purposes. This will be used as the index into the - // container holding the head structures. See SLASupportTree::Impl - long id = -1; - - // If there is a pillar connecting to this head, then the id will be set. - long pillar_id = -1; - - inline void invalidate() { id = -1; } - inline bool is_valid() const { return id >= 0; } - - Head(double r_big_mm, - double r_small_mm, - double length_mm, - double penetration, - Vec3d direction = {0, 0, -1}, // direction (normal to the dull end ) - Vec3d offset = {0, 0, 0}, // displacement - const size_t circlesteps = 45): - steps(circlesteps), dir(direction), tr(offset), - r_back_mm(r_big_mm), r_pin_mm(r_small_mm), width_mm(length_mm), - penetration_mm(penetration) - { - - // We create two spheres which will be connected with a robe that fits - // both circles perfectly. - - // Set up the model detail level - const double detail = 2*PI/steps; - - // We don't generate whole circles. Instead, we generate only the - // portions which are visible (not covered by the robe) To know the - // exact portion of the bottom and top circles we need to use some - // rules of tangent circles from which we can derive (using simple - // triangles the following relations: - - // The height of the whole mesh - const double h = r_big_mm + r_small_mm + width_mm; - double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); - - // To generate a whole circle we would pass a portion of (0, Pi) - // To generate only a half horizontal circle we can pass (0, Pi/2) - // The calculated phi is an offset to the half circles needed to smooth - // the transition from the circle to the robe geometry - - auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); - - for(auto& p : s2.points) z(p) += h; - - mesh.merge(s1); - mesh.merge(s2); - - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { - coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); - coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; - - mesh.indices.emplace_back(i1s1, i2s1, i2s2); - mesh.indices.emplace_back(i1s1, i2s2, i1s2); - } - - auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); - auto i2s1 = coord_t(s1.points.size()) - 1; - auto i1s2 = coord_t(s1.points.size()); - auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; - - mesh.indices.emplace_back(i2s2, i2s1, i1s1); - mesh.indices.emplace_back(i1s2, i2s2, i1s1); - - // 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) - for(auto& p : mesh.points) z(p) -= (h + r_small_mm - penetration_mm); - } - - void transform() - { - using Quaternion = Eigen::Quaternion; - - // We rotate the head to the specified direction The head's pointing - // side is facing upwards so this means that it would hold a support - // point with a normal pointing straight down. This is the reason of - // the -1 z coordinate - auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); - - for(auto& p : mesh.points) p = quatern * p + tr; - } - - double fullwidth() const { - return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; - } - - static double fullwidth(const SupportConfig& cfg) { - return 2 * cfg.head_front_radius_mm + cfg.head_width_mm + - 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; - } - - Vec3d junction_point() const { - return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; - } - - double request_pillar_radius(double radius) const { - const double rmax = r_back_mm; - return radius > 0 && radius < rmax ? radius : rmax; - } -}; - -struct Junction { - Contour3D mesh; - double r = 1; - size_t steps = 45; - Vec3d pos; - - long id = -1; - - Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): - r(r_mm), steps(stepnum), pos(tr) - { - mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); - for(auto& p : mesh.points) p += tr; - } -}; - -struct Pillar { - Contour3D mesh; - Contour3D base; - double r = 1; - size_t steps = 0; - Vec3d endpt; - double height = 0; - - long id = -1; - - // If the pillar connects to a head, this is the id of that head - bool starts_from_head = true; // Could start from a junction as well - long start_junction_id = -1; - - // How many bridges are connected to this pillar - unsigned bridges = 0; - - // How many pillars are cascaded with this one - unsigned links = 0; - - Pillar(const Vec3d& jp, const Vec3d& endp, - double radius = 1, size_t st = 45): - r(radius), steps(st), endpt(endp), starts_from_head(false) - { - assert(steps > 0); - - height = jp(Z) - endp(Z); - if(height > EPSILON) { // Endpoint is below the starting point - - // We just create a bridge geometry with the pillar parameters and - // move the data. - Contour3D body = cylinder(radius, height, st, endp); - mesh.points.swap(body.points); - mesh.indices.swap(body.indices); - } - } - - Pillar(const Junction& junc, const Vec3d& endp): - Pillar(junc.pos, endp, junc.r, junc.steps){} - - Pillar(const Head& head, const Vec3d& endp, double radius = 1): - Pillar(head.junction_point(), endp, head.request_pillar_radius(radius), - head.steps) - { - } - - inline Vec3d startpoint() const { - return {endpt(X), endpt(Y), endpt(Z) + height}; - } - - inline const Vec3d& endpoint() const { return endpt; } - - Pillar& add_base(double baseheight = 3, double radius = 2) { - if(baseheight <= 0) return *this; - if(baseheight > height) baseheight = height; - - assert(steps >= 0); - auto last = int(steps - 1); - - if(radius < r ) radius = r; - - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + r*std::cos(phi); - double y = endpt(Y) + r*std::sin(phi); - base.points.emplace_back(x, y, z); - } - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); - } - - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); - base.points.emplace_back(ep); - - auto& indices = base.indices; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - indices.emplace_back(i, i + 1, hcenter); - indices.emplace_back(lcenter, offs + i + 1, offs + i); - } - - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - indices.emplace_back(hcenter, last, 0); - indices.emplace_back(offs, offs + last, lcenter); - return *this; - } - - bool has_base() const { return !base.points.empty(); } -}; - -// A Bridge between two pillars (with junction endpoints) -struct Bridge { - Contour3D mesh; - double r = 0.8; - - long id = -1; - long start_jid = -1; - long end_jid = -1; - - // We should reduce the radius a tiny bit to help the convex hull algorithm - Bridge(const Vec3d& j1, const Vec3d& j2, - double r_mm = 0.8, size_t steps = 45): - r(r_mm) - { - using Quaternion = Eigen::Quaternion; - Vec3d dir = (j2 - j1).normalized(); - double d = distance(j2, j1); - - mesh = cylinder(r, d, steps); - - auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); - for(auto& p : mesh.points) p = quater * p + j1; - } - - Bridge(const Junction& j1, const Junction& j2, double r_mm = 0.8): - Bridge(j1.pos, j2.pos, r_mm, j1.steps) {} - -}; - -// A bridge that spans from model surface to model surface with small connecting -// edges on the endpoints. Used for headless support points. -struct CompactBridge { - Contour3D mesh; - long id = -1; - - CompactBridge(const Vec3d& sp, - const Vec3d& ep, - const Vec3d& n, - double r, - bool endball = true, - size_t steps = 45) - { - Vec3d startp = sp + r * n; - Vec3d dir = (ep - startp).normalized(); - Vec3d endp = ep - r * dir; - - Bridge br(startp, endp, r, steps); - mesh.merge(br.mesh); - - // now add the pins - double fa = 2*PI/steps; - auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); - for(auto& p : upperball.points) p += startp; - - if(endball) { - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; - mesh.merge(lowerball); - } - - mesh.merge(upperball); - } -}; - -// A wrapper struct around the base pool (pad) -struct Pad { - TriangleMesh tmesh; - PoolConfig cfg; - double zlevel = 0; - - Pad() = default; - - Pad(const TriangleMesh& support_mesh, - const ExPolygons& modelbase, - double ground_level, - const PoolConfig& pcfg) : - cfg(pcfg), - zlevel(ground_level + - sla::get_pad_fullheight(pcfg) - - sla::get_pad_elevation(pcfg)) - { - Polygons basep; - auto &thr = cfg.throw_on_cancel; - - thr(); - - // Get a sample for the pad from the support mesh - { - ExPolygons platetmp; - - float zstart = float(zlevel); - float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON); - - base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); - - // We don't need no... holes control... - for (const ExPolygon &bp : platetmp) - basep.emplace_back(std::move(bp.contour)); - } - - if(pcfg.embed_object) { - - // If the zero elevation mode is ON, we need to process the model - // base silhouette. Create the offsetted version and punch the - // breaksticks across its perimeter. - - ExPolygons modelbase_offs = modelbase; - - if (pcfg.embed_object.object_gap_mm > 0.0) - modelbase_offs - = offset_ex(modelbase_offs, - float(scaled(pcfg.embed_object.object_gap_mm))); - - // Create a spatial index of the support silhouette polygons. - // This will be used to check for intersections with the model - // silhouette polygons. If there is no intersection, then a certain - // part of the pad is redundant as it does not host any supports. - BoxIndex bindex; - { - unsigned idx = 0; - for(auto &bp : basep) { - auto bb = bp.bounding_box(); - bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); - bindex.insert(bb, idx++); - } - } - - ExPolygons concaveh = offset_ex( - concave_hull(basep, pcfg.max_merge_distance_mm, thr), - scaled(pcfg.min_wall_thickness_mm)); - - // Punching the breaksticks across the offsetted polygon perimeters - auto pad_stickholes = reserve_vector(modelbase.size()); - for(auto& poly : modelbase_offs) { - - bool overlap = false; - for (const ExPolygon &p : concaveh) - overlap = overlap || poly.overlaps(p); - - auto bb = poly.contour.bounding_box(); - bb.offset(scaled(pcfg.min_wall_thickness_mm)); - - std::vector qres = - bindex.query(bb, BoxIndex::qtIntersects); - - if (!qres.empty() || overlap) { - - // The model silhouette polygon 'poly' HAS an intersection - // with the support silhouettes. Include this polygon - // in the pad holes with the breaksticks and merge the - // original (offsetted) version with the rest of the pad - // base plate. - - basep.emplace_back(poly.contour); - - // The holes of 'poly' will become positive parts of the - // pad, so they has to be checked for intersections as well - // and erased if there is no intersection with the supports - auto it = poly.holes.begin(); - while(it != poly.holes.end()) { - if (bindex.query(it->bounding_box(), - BoxIndex::qtIntersects).empty()) - it = poly.holes.erase(it); - else - ++it; - } - - // Punch the breaksticks - sla::breakstick_holes( - poly, - pcfg.embed_object.object_gap_mm, // padding - pcfg.embed_object.stick_stride_mm, - pcfg.embed_object.stick_width_mm, - pcfg.embed_object.stick_penetration_mm); - - pad_stickholes.emplace_back(poly); - } - } - - create_base_pool(basep, tmesh, pad_stickholes, cfg); - } else { - for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); - create_base_pool(basep, tmesh, {}, cfg); - } - - tmesh.translate(0, 0, float(zlevel)); - if (!tmesh.empty()) tmesh.require_shared_vertices(); - } - - bool empty() const { return tmesh.facets_count() == 0; } -}; - -// The minimum distance for two support points to remain valid. -static const double /*constexpr*/ D_SP = 0.1; - -enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers - X, Y, Z -}; - -// 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 throw_on_cancel = [](){}, - const std::vector& selected_points = {}); - -inline Vec2d to_vec2(const Vec3d& v3) { - return {v3(X), v3(Y)}; -} - -bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { - return e1.second == e2.second; -} - -// Clustering a set of points by the given distance. -ClusteredPoints cluster(const std::vector& indices, - std::function pointfn, - double dist, - unsigned max_points); - -ClusteredPoints cluster(const PointSet& points, - double dist, - unsigned max_points); - -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - std::function predicate, - unsigned max_points); - -// This class will hold the support tree meshes with some additional bookkeeping -// as well. Various parts of the support geometry are stored separately and are -// merged when the caller queries the merged mesh. The merged result is cached -// for fast subsequent delivery of the merged mesh which can be quite complex. -// An object of this class will be used as the result type during the support -// generation algorithm. Parts will be added with the appropriate methods such -// as add_head or add_pillar which forwards the constructor arguments and fills -// the IDs of these substructures. The IDs are basically indices into the arrays -// of the appropriate type (heads, pillars, etc...). One can later query e.g. a -// pillar for a specific head... -// -// The support pad is considered an auxiliary geometry and is not part of the -// merged mesh. It can be retrieved using a dedicated method (pad()) -class SLASupportTree::Impl { - // For heads it is beneficial to use the same IDs as for the support points. - std::vector m_heads; - std::vector m_head_indices; - - std::vector m_pillars; - std::vector m_junctions; - std::vector m_bridges; - std::vector m_compact_bridges; - Controller m_ctl; - - Pad m_pad; - - using Mutex = ccr::Mutex; - - mutable Mutex m_mutex; - mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; - mutable double model_height = 0; // the full height of the model - -public: - double ground_level = 0; - - Impl() = default; - inline Impl(const Controller& ctl): m_ctl(ctl) {} - - const Controller& ctl() const { return m_ctl; } - - template Head& add_head(unsigned id, Args&&... args) - { - std::lock_guard lk(m_mutex); - m_heads.emplace_back(std::forward(args)...); - m_heads.back().id = id; - - if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); - m_head_indices[id] = m_heads.size() - 1; - - meshcache_valid = false; - return m_heads.back(); - } - - template Pillar& add_pillar(unsigned headid, Args&&... args) - { - std::lock_guard lk(m_mutex); - - assert(headid < m_head_indices.size()); - Head &head = m_heads[m_head_indices[headid]]; - - m_pillars.emplace_back(head, std::forward(args)...); - Pillar& pillar = m_pillars.back(); - pillar.id = long(m_pillars.size() - 1); - head.pillar_id = pillar.id; - pillar.start_junction_id = head.id; - pillar.starts_from_head = true; - - meshcache_valid = false; - return m_pillars.back(); - } - - void increment_bridges(const Pillar& pillar) - { - std::lock_guard lk(m_mutex); - assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - - if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) - m_pillars[size_t(pillar.id)].bridges++; - } - - void increment_links(const Pillar& pillar) - { - std::lock_guard lk(m_mutex); - assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - - if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) - m_pillars[size_t(pillar.id)].links++; - } - - template Pillar& add_pillar(Args&&...args) - { - std::lock_guard lk(m_mutex); - m_pillars.emplace_back(std::forward(args)...); - Pillar& pillar = m_pillars.back(); - pillar.id = long(m_pillars.size() - 1); - pillar.starts_from_head = false; - meshcache_valid = false; - return m_pillars.back(); - } - - const Head& pillar_head(long pillar_id) const - { - std::lock_guard lk(m_mutex); - assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); - - const Pillar& p = m_pillars[size_t(pillar_id)]; - assert(p.starts_from_head && p.start_junction_id >= 0); - assert(size_t(p.start_junction_id) < m_head_indices.size()); - - return m_heads[m_head_indices[p.start_junction_id]]; - } - - const Pillar& head_pillar(unsigned headid) const - { - std::lock_guard lk(m_mutex); - assert(headid < m_head_indices.size()); - - const Head& h = m_heads[m_head_indices[headid]]; - assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - - return m_pillars[size_t(h.pillar_id)]; - } - - template const Junction& add_junction(Args&&... args) - { - std::lock_guard lk(m_mutex); - m_junctions.emplace_back(std::forward(args)...); - m_junctions.back().id = long(m_junctions.size() - 1); - meshcache_valid = false; - return m_junctions.back(); - } - - template const Bridge& add_bridge(Args&&... args) - { - std::lock_guard lk(m_mutex); - m_bridges.emplace_back(std::forward(args)...); - m_bridges.back().id = long(m_bridges.size() - 1); - meshcache_valid = false; - return m_bridges.back(); - } - - template const CompactBridge& add_compact_bridge(Args&&...args) - { - std::lock_guard lk(m_mutex); - m_compact_bridges.emplace_back(std::forward(args)...); - m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); - meshcache_valid = false; - return m_compact_bridges.back(); - } - - Head &head(unsigned id) - { - std::lock_guard lk(m_mutex); - assert(id < m_head_indices.size()); - - meshcache_valid = false; - return m_heads[m_head_indices[id]]; - } - - inline size_t pillarcount() const { - std::lock_guard lk(m_mutex); - return m_pillars.size(); - } - - template inline IntegerOnly pillar(T id) const - { - std::lock_guard lk(m_mutex); - assert(id >= 0 && size_t(id) < m_pillars.size() && - size_t(id) < std::numeric_limits::max()); - - return m_pillars[size_t(id)]; - } - - const Pad &create_pad(const TriangleMesh &object_supports, - const ExPolygons & modelbase, - const PoolConfig & cfg) - { - m_pad = Pad(object_supports, modelbase, ground_level, cfg); - return m_pad; - } - - void remove_pad() { m_pad = Pad(); } - - const Pad& pad() const { return m_pad; } - - // WITHOUT THE PAD!!! - const TriangleMesh &merged_mesh() const - { - if (meshcache_valid) return meshcache; - - Contour3D merged; - - for (auto &head : m_heads) { - if (m_ctl.stopcondition()) break; - if (head.is_valid()) merged.merge(head.mesh); - } - - for (auto &stick : m_pillars) { - if (m_ctl.stopcondition()) break; - merged.merge(stick.mesh); - merged.merge(stick.base); - } - - for (auto &j : m_junctions) { - if (m_ctl.stopcondition()) break; - merged.merge(j.mesh); - } - - for (auto &cb : m_compact_bridges) { - if (m_ctl.stopcondition()) break; - merged.merge(cb.mesh); - } - - for (auto &bs : m_bridges) { - if (m_ctl.stopcondition()) break; - merged.merge(bs.mesh); - } - - if (m_ctl.stopcondition()) { - // In case of failure we have to return an empty mesh - meshcache = TriangleMesh(); - return meshcache; - } - - meshcache = mesh(merged); - - // The mesh will be passed by const-pointer to TriangleMeshSlicer, - // which will need this. - if (!meshcache.empty()) meshcache.require_shared_vertices(); - - BoundingBoxf3 &&bb = meshcache.bounding_box(); - model_height = bb.max(Z) - bb.min(Z); - - meshcache_valid = true; - return meshcache; - } - - // WITH THE PAD - double full_height() const - { - if (merged_mesh().empty() && !pad().empty()) - return get_pad_fullheight(pad().cfg); - - double h = mesh_height(); - if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg); - return h; - } - - // WITHOUT THE PAD!!! - double mesh_height() const - { - if (!meshcache_valid) merged_mesh(); - return model_height; - } - - // Intended to be called after the generation is fully complete - void merge_and_cleanup() - { - merged_mesh(); // in case the mesh is not generated, it should be... - - // Doing clear() does not garantee to release the memory. - m_heads = {}; - m_head_indices = {}; - m_pillars = {}; - m_junctions = {}; - m_bridges = {}; - m_compact_bridges = {}; - } -}; - -// This function returns the position of the centroid in the input 'clust' -// vector of point indices. -template -long cluster_centroid(const ClusterEl& clust, - std::function pointfn, - DistFn df) -{ - switch(clust.size()) { - case 0: /* empty cluster */ return -1; - case 1: /* only one element */ return 0; - case 2: /* if two elements, there is no center */ return 0; - default: ; - } - - // The function works by calculating for each point the average distance - // from all the other points in the cluster. We create a selector bitmask of - // the same size as the cluster. The bitmask will have two true bits and - // false bits for the rest of items and we will loop through all the - // permutations of the bitmask (combinations of two points). Get the - // distance for the two points and add the distance to the averages. - // The point with the smallest average than wins. - - // The complexity should be O(n^2) but we will mostly apply this function - // for small clusters only (cca 3 elements) - - std::vector sel(clust.size(), false); // create full zero bitmask - std::fill(sel.end() - 2, sel.end(), true); // insert the two ones - std::vector avgs(clust.size(), 0.0); // store the average distances - - do { - std::array idx; - for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; - - double d = df(pointfn(clust[idx[0]]), - pointfn(clust[idx[1]])); - - // add the distance to the sums for both associated points - for(auto i : idx) avgs[i] += d; - - // now continue with the next permutation of the bitmask with two 1s - } while(std::next_permutation(sel.begin(), sel.end())); - - // Divide by point size in the cluster to get the average (may be redundant) - for(auto& a : avgs) a /= clust.size(); - - // get the lowest average distance and return the index - auto minit = std::min_element(avgs.begin(), avgs.end()); - return long(minit - avgs.begin()); -} - -inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { - return (endp - startp).normalized(); -} - -class SLASupportTree::Algorithm { - const SupportConfig& m_cfg; - const EigenMesh3D& m_mesh; - const std::vector& m_support_pts; - - using PtIndices = std::vector; - - PtIndices m_iheads; // support points with pinhead - PtIndices m_iheadless; // headless support points - - // supp. pts. connecting to model: point index and the ray hit data - std::vector> m_iheads_onmodel; - - // normals for support points from model faces. - PointSet m_support_nmls; - - // Clusters of points which can reach the ground directly and can be - // bridged to one central pillar - std::vector m_pillar_clusters; - - // This algorithm uses the Impl class as its output stream. It will be - // filled gradually with support elements (heads, pillars, bridges, ...) - using Result = SLASupportTree::Impl; - - Result& m_result; - - // support points in Eigen/IGL format - PointSet m_points; - - // throw if canceled: It will be called many times so a shorthand will - // come in handy. - ThrowOnCancel m_thr; - - // A spatial index to easily find strong pillars to connect to. - PointIndex m_pillar_index; - - inline double ray_mesh_intersect(const Vec3d& s, - const Vec3d& dir) - { - return m_mesh.query_ray_hit(s, dir).distance(); - } - - // This function will test if a future pinhead would not collide with the - // model geometry. It does not take a 'Head' object because those are - // created after this test. Parameters: s: The touching point on the model - // surface. dir: This is the direction of the head from the pin to the back - // r_pin, r_back: the radiuses of the pin and the back sphere width: This - // is the full width from the pin center to the back center m: The object - // mesh. - // The return value is the hit result from the ray casting. If the starting - // point was inside the model, an "invalid" hit_result will be returned - // with a zero distance value instead of a NAN. This way the result can - // be used safely for comparison with other distances. - EigenMesh3D::hit_result pinhead_mesh_intersect( - const Vec3d& s, - const Vec3d& dir, - double r_pin, - double r_back, - double width) - { - static const size_t SAMPLES = 8; - - // method based on: - // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space - - // We will shoot multiple rays from the head pinpoint in the direction - // of the pinhead robe (side) surface. The result will be the smallest - // hit distance. - - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. - Vec3d v = dir; // Our direction (axis) - Vec3d c = s + width * dir; - const double& sd = m_cfg.safety_distance_mm; - - // Two vectors that will be perpendicular to each other and to the - // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a - // placeholder. - Vec3d a(0, 1, 0), b; - - // The portions of the circle (the head-back circle) for which we will - // shoot rays. - std::array phis; - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - auto& m = m_mesh; - using HitResult = EigenMesh3D::hit_result; - - // Hit results - std::array hits; - - // We have to address the case when the direction vector v (same as - // dir) is coincident with one of the world axes. In this case two of - // its components will be completely zero and one is 1.0. Our method - // becomes dangerous here due to division with zero. Instead, vector - // 'a' can be an element-wise rotated version of 'v' - auto chk1 = [] (double val) { - return std::abs(std::abs(val) - 1) < 1e-20; - }; - - if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { - a = {v(Z), v(X), v(Y)}; - b = {v(Y), v(Z), v(X)}; - } - else { - a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); - b = a.cross(v); - } - - // Now a and b vectors are perpendicular to v and to each other. - // Together they define the plane where we have to iterate with the - // given angles in the 'phis' vector - ccr_par::enumerate(phis.begin(), phis.end(), - [&hits, &m, sd, r_pin, r_back, s, a, b, c] - (double phi, size_t i) - { - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rpscos = (sd + r_pin) * cosphi; - double rpssin = (sd + r_pin) * sinphi; - double rpbcos = (sd + r_back) * cosphi; - double rpbsin = (sd + r_back) * sinphi; - - // Point on the circle on the pin sphere - Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), - s(Y) + rpscos * a(Y) + rpssin * b(Y), - s(Z) + rpscos * a(Z) + rpssin * b(Z)); - - // Point ps is not on mesh but can be inside or outside as well. - // This would cause many problems with ray-casting. To detect the - // position we will use the ray-casting result (which has an - // is_inside predicate). - - // This is the point on the circle on the back sphere - Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), - c(Y) + rpbcos * a(Y) + rpbsin * b(Y), - c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); - - Vec3d n = (p - ps).normalized(); - auto q = m.query_ray_hit(ps + sd*n, n); - - if(q.is_inside()) { // the hit is inside the model - if(q.distance() > r_pin + sd) { - // If we are inside the model and the hit distance is bigger - // than our pin circle diameter, it probably indicates that - // the support point was already inside the model, or there - // is really no space around the point. We will assign a - // zero hit distance to these cases which will enforce the - // function return value to be an invalid ray with zero hit - // distance. (see min_element at the end) - hits[i] = HitResult(0.0); - } - else { - // re-cast the ray from the outside of the object. - // The starting point has an offset of 2*safety_distance - // because the original ray has also had an offset - auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); - hits[i] = q2; - } - } else hits[i] = q; - }); - - auto mit = std::min_element(hits.begin(), hits.end()); - - return *mit; - } - - // Checking bridge (pillar and stick as well) intersection with the model. - // If the function is used for headless sticks, the ins_check parameter - // have to be true as the beginning of the stick might be inside the model - // geometry. - // The return value is the hit result from the ray casting. If the starting - // point was inside the model, an "invalid" hit_result will be returned - // with a zero distance value instead of a NAN. This way the result can - // be used safely for comparison with other distances. - EigenMesh3D::hit_result bridge_mesh_intersect( - const Vec3d& s, - const Vec3d& dir, - double r, - bool ins_check = false) - { - static const size_t SAMPLES = 8; - - // helper vector calculations - Vec3d a(0, 1, 0), b; - const double& sd = m_cfg.safety_distance_mm; - - // INFO: for explanation of the method used here, see the previous - // method's comments. - - auto chk1 = [] (double val) { - return std::abs(std::abs(val) - 1) < 1e-20; - }; - - if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { - a = {dir(Z), dir(X), dir(Y)}; - b = {dir(Y), dir(Z), dir(X)}; - } - else { - a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); - b = a.cross(dir); - } - - // circle portions - std::array phis; - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - auto& m = m_mesh; - using HitResult = EigenMesh3D::hit_result; - - // Hit results - std::array hits; - - ccr_par::enumerate(phis.begin(), phis.end(), - [&m, a, b, sd, dir, r, s, ins_check, &hits] - (double phi, size_t i) - { - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rcos = (sd + r) * cosphi; - double rsin = (sd + r) * sinphi; - - // Point on the circle on the pin sphere - Vec3d p (s(X) + rcos * a(X) + rsin * b(X), - s(Y) + rcos * a(Y) + rsin * b(Y), - s(Z) + rcos * a(Z) + rsin * b(Z)); - - auto hr = m.query_ray_hit(p + sd*dir, dir); - - if(ins_check && hr.is_inside()) { - if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); - else { - // re-cast the ray from the outside of the object - auto hr2 = - m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); - - hits[i] = hr2; - } - } else hits[i] = hr; - }); - - auto mit = std::min_element(hits.begin(), hits.end()); - - return *mit; - } - - // Helper function for interconnecting two pillars with zig-zag bridges. - bool interconnect(const Pillar& pillar, const Pillar& nextpillar) - { - // We need to get the starting point of the zig-zag pattern. We have to - // be aware that the two head junctions are at different heights. We - // may start from the lowest junction and call it a day but this - // strategy would leave unconnected a lot of pillar duos where the - // shorter pillar is too short to start a new bridge but the taller - // pillar could still be bridged with the shorter one. - bool was_connected = false; - - Vec3d supper = pillar.startpoint(); - Vec3d slower = nextpillar.startpoint(); - Vec3d eupper = pillar.endpoint(); - Vec3d elower = nextpillar.endpoint(); - - double zmin = m_result.ground_level + m_cfg.base_height_mm; - eupper(Z) = std::max(eupper(Z), zmin); - elower(Z) = std::max(elower(Z), zmin); - - // The usable length of both pillars should be positive - if(slower(Z) - elower(Z) < 0) return false; - if(supper(Z) - eupper(Z) < 0) return false; - - double pillar_dist = distance(Vec2d{slower(X), slower(Y)}, - Vec2d{supper(X), supper(Y)}); - double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); - double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); - - if(pillar_dist < 2 * m_cfg.head_back_radius_mm || - pillar_dist > m_cfg.max_pillar_link_distance_mm) return false; - - if(supper(Z) < slower(Z)) supper.swap(slower); - if(eupper(Z) < elower(Z)) eupper.swap(elower); - - double startz = 0, endz = 0; - - startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); - endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); - - if(slower(Z) - eupper(Z) < std::abs(zstep)) { - // no space for even one cross - - // Get max available space - startz = std::min(supper(Z), slower(Z) - zstep); - endz = std::max(eupper(Z) + zstep, elower(Z)); - - // Align to center - double available_dist = (startz - endz); - double rounds = std::floor(available_dist / std::abs(zstep)); - startz -= 0.5 * (available_dist - rounds * std::abs(zstep));; - } - - auto pcm = m_cfg.pillar_connection_mode; - bool docrosses = - pcm == PillarConnectionMode::cross || - (pcm == PillarConnectionMode::dynamic && - pillar_dist > 2*m_cfg.base_radius_mm); - - // 'sj' means starting junction, 'ej' is the end junction of a bridge. - // They will be swapped in every iteration thus the zig-zag pattern. - // According to a config parameter, a second bridge may be added which - // results in a cross connection between the pillars. - Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; - - // TODO: This is a workaround to not have a faulty last bridge - while(ej(Z) >= eupper(Z) /*endz*/) { - if(bridge_mesh_intersect(sj, - dirv(sj, ej), - pillar.r) >= bridge_distance) - { - m_result.add_bridge(sj, ej, pillar.r); - was_connected = true; - } - - // double bridging: (crosses) - if(docrosses) { - Vec3d sjback(ej(X), ej(Y), sj(Z)); - Vec3d ejback(sj(X), sj(Y), ej(Z)); - if(sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) && - bridge_mesh_intersect(sjback, - dirv(sjback, ejback), - pillar.r) >= bridge_distance) - { - // need to check collision for the cross stick - m_result.add_bridge(sjback, ejback, pillar.r); - was_connected = true; - } - } - - sj.swap(ej); - ej(Z) = sj(Z) + zstep; - } - - return was_connected; - } - - // For connecting a head to a nearby pillar. - bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - - auto nearpillar = [this, nearpillar_id]() { - return m_result.pillar(nearpillar_id); - }; - - if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; - - Vec3d headjp = head.junction_point(); - Vec3d nearjp_u = nearpillar().startpoint(); - Vec3d nearjp_l = nearpillar().endpoint(); - - double r = head.r_back_mm; - double d2d = distance(to_2d(headjp), to_2d(nearjp_u)); - double d3d = distance(headjp, nearjp_u); - - double hdiff = nearjp_u(Z) - headjp(Z); - double slope = std::atan2(hdiff, d2d); - - Vec3d bridgestart = headjp; - Vec3d bridgeend = nearjp_u; - double max_len = m_cfg.max_bridge_length_mm; - double max_slope = m_cfg.bridge_slope; - double zdiff = 0.0; - - // check the default situation if feasible for a bridge - if(d3d > max_len || slope > -max_slope) { - // not feasible to connect the two head junctions. We have to search - // for a suitable touch point. - - double Zdown = headjp(Z) + d2d * std::tan(-max_slope); - Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; - double D = distance(headjp, touchjp); - zdiff = Zdown - nearjp_u(Z); - - if(zdiff > 0) { - Zdown -= zdiff; - bridgestart(Z) -= zdiff; - touchjp(Z) = Zdown; - - double t = bridge_mesh_intersect(headjp, {0,0,-1}, r); - - // We can't insert a pillar under the source head to connect - // with the nearby pillar's starting junction - if(t < zdiff) return false; - } - - if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len) - bridgeend(Z) = Zdown; - else - return false; - } - - // There will be a minimum distance from the ground where the - // bridge is allowed to connect. This is an empiric value. - double minz = m_result.ground_level + 2 * m_cfg.head_width_mm; - if(bridgeend(Z) < minz) return false; - - double t = bridge_mesh_intersect(bridgestart, - dirv(bridgestart, bridgeend), r); - - // Cannot insert the bridge. (further search might not worth the hassle) - if(t < distance(bridgestart, bridgeend)) return false; - - // A partial pillar is needed under the starting head. - if(zdiff > 0) { - m_result.add_pillar(unsigned(head.id), bridgestart, r); - m_result.add_junction(bridgestart, r); - } - - m_result.add_bridge(bridgestart, bridgeend, r); - m_result.increment_bridges(nearpillar()); - - return true; - } - - bool search_pillar_and_connect(const Head& head) { - PointIndex spindex = m_pillar_index; - - long nearest_id = -1; - - Vec3d querypoint = head.junction_point(); - - while(nearest_id < 0 && !spindex.empty()) { m_thr(); - // loop until a suitable head is not found - // if there is a pillar closer than the cluster center - // (this may happen as the clustering is not perfect) - // than we will bridge to this closer pillar - - Vec3d qp(querypoint(X), querypoint(Y), m_result.ground_level); - auto qres = spindex.nearest(qp, 1); - if(qres.empty()) break; - - auto ne = qres.front(); - nearest_id = ne.second; - - if(nearest_id >= 0) { - auto nearpillarID = unsigned(nearest_id); - if(nearpillarID < m_result.pillarcount()) { - if(!connect_to_nearpillar(head, nearpillarID)) { - nearest_id = -1; // continue searching - spindex.remove(ne); // without the current pillar - } - } - } - } - - return nearest_id >= 0; - } - - // This is a proxy function for pillar creation which will mind the gap - // between the pad and the model bottom in zero elevation mode. - void create_ground_pillar(const Vec3d &jp, - const Vec3d &sourcedir, - double radius, - int head_id = -1) - { - // People were killed for this number (seriously) - static const double SQR2 = std::sqrt(2.0); - static const Vec3d DOWN = {0.0, 0.0, -1.0}; - - double gndlvl = m_result.ground_level; - Vec3d endp = {jp(X), jp(Y), gndlvl}; - double sd = m_cfg.pillar_base_safety_distance_mm; - int pillar_id = -1; - double min_dist = sd + m_cfg.base_radius_mm + EPSILON; - double dist = 0; - bool can_add_base = true; - bool normal_mode = true; - - if (m_cfg.object_elevation_mm < EPSILON - && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { - // Get the distance from the mesh. This can be later optimized - // to get the distance in 2D plane because we are dealing with - // the ground level only. - - normal_mode = false; - double mv = min_dist - dist; - double azimuth = std::atan2(sourcedir(Y), sourcedir(X)); - double sinpolar = std::sin(PI - m_cfg.bridge_slope); - double cospolar = std::cos(PI - m_cfg.bridge_slope); - double cosazm = std::cos(azimuth); - double sinazm = std::sin(azimuth); - - auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) - .normalized(); - - using namespace libnest2d::opt; - StopCriteria scr; - scr.stop_score = min_dist; - SubplexOptimizer solver(scr); - - auto result = solver.optimize_max( - [this, dir, jp, gndlvl](double mv) { - Vec3d endp = jp + SQR2 * mv * dir; - endp(Z) = gndlvl; - return std::sqrt(m_mesh.squared_distance(endp)); - }, - initvals(mv), bound(0.0, 2 * min_dist)); - - mv = std::get<0>(result.optimum); - endp = jp + SQR2 * mv * dir; - Vec3d pgnd = {endp(X), endp(Y), gndlvl}; - can_add_base = result.score > min_dist; - - double gnd_offs = m_mesh.ground_level_offset(); - auto abort_in_shame = - [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() - { - normal_mode = true; - can_add_base = false; // Nothing left to do, hope for the best - endp = {jp(X), jp(Y), gndlvl - gnd_offs }; - }; - - // We have to check if the bridge is feasible. - if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) - abort_in_shame(); - else { - // If the new endpoint is below ground, do not make a pillar - if (endp(Z) < gndlvl) - endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off - else { - - auto hit = bridge_mesh_intersect(endp, DOWN, radius); - if (!std::isinf(hit.distance())) abort_in_shame(); - - Pillar &plr = m_result.add_pillar(endp, pgnd, radius); - - if (can_add_base) - plr.add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - - pillar_id = plr.id; - } - - m_result.add_bridge(jp, endp, radius); - m_result.add_junction(endp, radius); - - // Add a degenerated pillar and the bridge. - // The degenerate pillar will have zero length and it will - // prevent from queries of head_pillar() to have non-existing - // pillar when the head should have one. - if (head_id >= 0) - m_result.add_pillar(unsigned(head_id), jp, radius); - } - } - - if (normal_mode) { - Pillar &plr = head_id >= 0 - ? m_result.add_pillar(unsigned(head_id), - endp, - radius) - : m_result.add_pillar(jp, endp, radius); - - if (can_add_base) - plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); - - pillar_id = plr.id; - } - - if(pillar_id >= 0) // Save the pillar endpoint in the spatial index - m_pillar_index.insert(endp, pillar_id); - } - -public: - - Algorithm(const SupportConfig& config, - const EigenMesh3D& emesh, - const std::vector& support_pts, - Result& result, - ThrowOnCancel thr) : - m_cfg(config), - m_mesh(emesh), - m_support_pts(support_pts), - m_support_nmls(support_pts.size(), 3), - m_result(result), - m_points(support_pts.size(), 3), - m_thr(thr) - { - // Prepare the support points in Eigen/IGL format as well, we will use - // it mostly in this form. - - long i = 0; - for(const SupportPoint& sp : m_support_pts) { - m_points.row(i)(X) = double(sp.pos(X)); - m_points.row(i)(Y) = double(sp.pos(Y)); - m_points.row(i)(Z) = double(sp.pos(Z)); - ++i; - } - } - - - // Now let's define the individual steps of the support generation algorithm - - // Filtering step: here we will discard inappropriate support points - // and decide the future of the appropriate ones. We will check if a - // pinhead is applicable and adjust its angle at each support point. We - // will also merge the support points that are just too close and can - // be considered as one. - void filter() { - // Get the points that are too close to each other and keep only the - // first one - auto aliases = cluster(m_points, D_SP, 2); - - PtIndices filtered_indices; - filtered_indices.reserve(aliases.size()); - m_iheads.reserve(aliases.size()); - m_iheadless.reserve(aliases.size()); - for(auto& a : aliases) { - // Here we keep only the front point of the cluster. - filtered_indices.emplace_back(a.front()); - } - - // calculate the normals to the triangles for filtered points - auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm, - m_thr, filtered_indices); - - // Not all of the support points have to be a valid position for - // support creation. The angle may be inappropriate or there may - // not be enough space for the pinhead. Filtering is applied for - // these reasons. - - using libnest2d::opt::bound; - using libnest2d::opt::initvals; - using libnest2d::opt::GeneticOptimizer; - using libnest2d::opt::StopCriteria; - - ccr::Mutex mutex; - auto addfn = [&mutex](PtIndices &container, unsigned val) { - std::lock_guard lk(mutex); - container.emplace_back(val); - }; - - ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), - [this, &nmls, addfn](unsigned fidx, size_t i) - { - m_thr(); - - auto n = nmls.row(i); - - // for all normals we generate the spherical coordinates and - // saturate the polar angle to 45 degrees from the bottom then - // convert back to standard coordinates to get the new normal. - // Then we just create a quaternion from the two normals - // (Quaternion::FromTwoVectors) and apply the rotation to the - // arrow head. - - double z = n(2); - double r = 1.0; // for normalized vector - double polar = std::acos(z / r); - double azimuth = std::atan2(n(1), n(0)); - - // skip if the tilt is not sane - if(polar >= PI - m_cfg.normal_cutoff_angle) { - - // We saturate the polar angle to 3pi/4 - polar = std::max(polar, 3*PI / 4); - - // save the head (pinpoint) position - Vec3d hp = m_points.row(fidx); - - double w = m_cfg.head_width_mm + - m_cfg.head_back_radius_mm + - 2*m_cfg.head_front_radius_mm; - - double pin_r = double(m_support_pts[fidx].head_front_radius); - - // Reassemble the now corrected normal - auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), - std::sin(azimuth) * std::sin(polar), - std::cos(polar)).normalized(); - - // check available distance - EigenMesh3D::hit_result t - = pinhead_mesh_intersect(hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); - - if(t.distance() <= w) { - - // Let's try to optimize this angle, there might be a - // viable normal that doesn't collide with the model - // geometry and its very close to the default. - - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = w; // space greater than w is enough - GeneticOptimizer solver(stc); - solver.seed(0); // we want deterministic behavior - - auto oresult = solver.optimize_max( - [this, pin_r, w, hp](double plr, double azm) - { - auto n = Vec3d(std::cos(azm) * std::sin(plr), - std::sin(azm) * std::sin(plr), - std::cos(plr)).normalized(); - - double score = pinhead_mesh_intersect( - hp, n, pin_r, m_cfg.head_back_radius_mm, w); - - return score; - }, - initvals(polar, azimuth), // start with what we have - bound(3*PI/4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); - - if(oresult.score > w) { - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - nn = Vec3d(std::cos(azimuth) * std::sin(polar), - std::sin(azimuth) * std::sin(polar), - std::cos(polar)).normalized(); - t = oresult.score; - } - } - - // save the verified and corrected normal - m_support_nmls.row(fidx) = nn; - - if (t.distance() > w) { - // Check distance from ground, we might have zero elevation. - if (hp(Z) + w * nn(Z) < m_result.ground_level) { - addfn(m_iheadless, fidx); - } else { - // mark the point for needing a head. - addfn(m_iheads, fidx); - } - } else if (polar >= 3 * PI / 4) { - // Headless supports do not tilt like the headed ones - // so the normal should point almost to the ground. - addfn(m_iheadless, fidx); - } - } - }); - - m_thr(); - } - - // Pinhead creation: based on the filtering results, the Head objects - // will be constructed (together with their triangle meshes). - void add_pinheads() - { - for (unsigned i : m_iheads) { - m_thr(); - m_result.add_head( - i, - m_cfg.head_back_radius_mm, - m_support_pts[i].head_front_radius, - m_cfg.head_width_mm, - m_cfg.head_penetration_mm, - m_support_nmls.row(i), // dir - m_support_pts[i].pos.cast() // displacement - ); - } - } - - // Further classification of the support points with pinheads. If the - // ground is directly reachable through a vertical line parallel to the - // Z axis we consider a support point as pillar candidate. If touches - // the model geometry, it will be marked as non-ground facing and - // further steps will process it. Also, the pillars will be grouped - // into clusters that can be interconnected with bridges. Elements of - // these groups may or may not be interconnected. Here we only run the - // clustering algorithm. - void classify() - { - // We should first get the heads that reach the ground directly - PtIndices ground_head_indices; - ground_head_indices.reserve(m_iheads.size()); - m_iheads_onmodel.reserve(m_iheads.size()); - - // First we decide which heads reach the ground and can be full - // pillars and which shall be connected to the model surface (or - // search a suitable path around the surface that leads to the - // ground -- TODO) - for(unsigned i : m_iheads) { - m_thr(); - - auto& head = m_result.head(i); - Vec3d n(0, 0, -1); - double r = head.r_back_mm; - Vec3d headjp = head.junction_point(); - - // collision check - auto hit = bridge_mesh_intersect(headjp, n, r); - - if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); - else if(m_cfg.ground_facing_only) head.invalidate(); - else m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); - } - - // We want to search for clusters of points that are far enough - // from each other in the XY plane to not cross their pillar bases - // These clusters of support points will join in one pillar, - // possibly in their centroid support point. - - auto pointfn = [this](unsigned i) { - return m_result.head(i).junction_point(); - }; - - auto predicate = [this](const PointIndexEl &e1, - const PointIndexEl &e2) { - double d2d = distance(to_2d(e1.first), to_2d(e2.first)); - double d3d = distance(e1.first, e2.first); - return d2d < 2 * m_cfg.base_radius_mm - && d3d < m_cfg.max_bridge_length_mm; - }; - - m_pillar_clusters = cluster(ground_head_indices, - pointfn, - predicate, - m_cfg.max_bridges_on_pillar); - } - - // Step: Routing the ground connected pinheads, and interconnecting - // them with additional (angled) bridges. Not all of these pinheads - // will be a full pillar (ground connected). Some will connect to a - // nearby pillar using a bridge. The max number of such side-heads for - // a central pillar is limited to avoid bad weight distribution. - void routing_to_ground() - { - const double pradius = m_cfg.head_back_radius_mm; - // const double gndlvl = m_result.ground_level; - - ClusterEl cl_centroids; - cl_centroids.reserve(m_pillar_clusters.size()); - - for(auto& cl : m_pillar_clusters) { m_thr(); - // place all the centroid head positions into the index. We - // will query for alternative pillar positions. If a sidehead - // cannot connect to the cluster centroid, we have to search - // for another head with a full pillar. Also when there are two - // elements in the cluster, the centroid is arbitrary and the - // sidehead is allowed to connect to a nearby pillar to - // increase structural stability. - - if(cl.empty()) continue; - - // get the current cluster centroid - auto& thr = m_thr; const auto& points = m_points; - long lcid = cluster_centroid(cl, - [&points](size_t idx) { return points.row(long(idx)); }, - [thr](const Vec3d& p1, const Vec3d& p2) - { - thr(); - return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y))); - }); - - assert(lcid >= 0); - unsigned hid = cl[size_t(lcid)]; // Head ID - - cl_centroids.emplace_back(hid); - - Head& h = m_result.head(hid); - h.transform(); - - create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); - } - - // now we will go through the clusters ones again and connect the - // sidepoints with the cluster centroid (which is a ground pillar) - // or a nearby pillar if the centroid is unreachable. - size_t ci = 0; - for(auto cl : m_pillar_clusters) { m_thr(); - - auto cidx = cl_centroids[ci++]; - - // TODO: don't consider the cluster centroid but calculate a - // central position where the pillar can be placed. this way - // the weight is distributed more effectively on the pillar. - - auto centerpillarID = m_result.head_pillar(cidx).id; - - for(auto c : cl) { m_thr(); - if(c == cidx) continue; - - auto& sidehead = m_result.head(c); - sidehead.transform(); - - if(!connect_to_nearpillar(sidehead, centerpillarID) && - !search_pillar_and_connect(sidehead)) - { - Vec3d pstart = sidehead.junction_point(); - //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; - // Could not find a pillar, create one - create_ground_pillar(pstart, - sidehead.dir, - pradius, - sidehead.id); - } - } - } - } - - // Step: routing the pinheads that would connect to the model surface - // along the Z axis downwards. For now these will actually be connected with - // the model surface with a flipped pinhead. In the future here we could use - // some smart algorithms to search for a safe path to the ground or to a - // nearby pillar that can hold the supported weight. - void routing_to_model() - { - - // We need to check if there is an easy way out to the bed surface. - // If it can be routed there with a bridge shorter than - // min_bridge_distance. - - // First we want to index the available pillars. The best is to connect - // these points to the available pillars - - auto routedown = [this](Head& head, const Vec3d& dir, double dist) - { - head.transform(); - Vec3d hjp = head.junction_point(); - Vec3d endp = hjp + dist * dir; - m_result.add_bridge(hjp, endp, head.r_back_mm); - m_result.add_junction(endp, head.r_back_mm); - - this->create_ground_pillar(endp, dir, head.r_back_mm); - }; - - std::vector modelpillars; - ccr::Mutex mutex; - - // TODO: connect these to the ground pillars if possible - ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), - [this, routedown, &modelpillars, &mutex] - (const std::pair &el, - size_t) - { - m_thr(); - unsigned idx = el.first; - EigenMesh3D::hit_result hit = el.second; - - auto& head = m_result.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 - // ///////////////////////////////////////////////////////////////// - - // Straight path failed so we will try to search for a suitable - // direction out of the cavity. - - // Get the spherical representation of the normal. its easier to - // work with. - double z = head.dir(Z); - double r = 1.0; // for normalized vector - double polar = std::acos(z / r); - double azimuth = std::atan2(head.dir(Y), head.dir(X)); - - using libnest2d::opt::bound; - using libnest2d::opt::initvals; - using libnest2d::opt::GeneticOptimizer; - using libnest2d::opt::StopCriteria; - - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = 1e6; - GeneticOptimizer solver(stc); - solver.seed(0); // we want deterministic behavior - - double r_back = head.r_back_mm; - - auto oresult = solver.optimize_max( - [this, hjp, r_back](double plr, double azm) - { - Vec3d n = Vec3d(std::cos(azm) * std::sin(plr), - std::sin(azm) * std::sin(plr), - std::cos(plr)).normalized(); - return bridge_mesh_intersect(hjp, n, r_back); - }, - initvals(polar, azimuth), // let's start with what we have - bound(3*PI/4, PI), // Must not exceed the slope limit - bound(-PI, PI) // azimuth can be a full range search - ); - - d = 0; t = oresult.score; - - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), - std::sin(azimuth) * std::sin(polar), - std::cos(polar)).normalized(); - - t = std::min(t, m_cfg.max_bridge_length_mm); - - while(d < t && !std::isinf(tdown = bridge_mesh_intersect( - hjp + d*bridgedir, - dirdown, - head.r_back_mm))) { - d += head.r_back_mm; - } - - if(std::isinf(tdown)) { // we heave found a route to the ground - routedown(head, bridgedir, d); return; - } - - // ///////////////////////////////////////////////////////////////// - // Route to model body - // ///////////////////////////////////////////////////////////////// - - double zangle = std::asin(hit.direction()(Z)); - zangle = std::max(zangle, PI/4); - double h = std::sin(zangle) * head.fullwidth(); - - // The width of the tail head that we would like to have... - h = std::min(hit.distance() - head.r_back_mm, h); - - if(h > 0) { - Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; - auto center_hit = m_mesh.query_ray_hit(hjp, dirdown); - - double hitdiff = center_hit.distance() - hit.distance(); - Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? - center_hit.position() : hit.position(); - - head.transform(); - - Pillar& pill = m_result.add_pillar(unsigned(head.id), - endp, - head.r_back_mm); - - Vec3d taildir = endp - hitp; - double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; - double w = dist - 2 * head.r_pin_mm - head.r_back_mm; - - Head tailhead(head.r_back_mm, - head.r_pin_mm, - w, - m_cfg.head_penetration_mm, - taildir, - hitp); - - tailhead.transform(); - pill.base = tailhead.mesh; - - // Experimental: add the pillar to the index for cascading - std::lock_guard lk(mutex); - modelpillars.emplace_back(unsigned(pill.id)); - return; - } - - // We have failed to route this head. - BOOST_LOG_TRIVIAL(warning) - << "Failed to route model facing support point." - << " ID: " << idx; - head.invalidate(); - }); - - for(auto pillid : modelpillars) { - auto& pillar = m_result.pillar(pillid); - m_pillar_index.insert(pillar.endpoint(), pillid); - } - } - - // Helper function for interconnect_pillars where pairs of already connected - // pillars should be checked for not to be processed again. This can be done - // in O(log) or even constant time with a set or an unordered set of hash - // values uniquely representing a pair of integers. The order of numbers - // within the pair should not matter, it has the same unique hash. - template static I pairhash(I a, I b) - { - using std::ceil; using std::log2; using std::max; using std::min; - - static_assert(std::is_integral::value, - "This function works only for integral types."); - - I g = min(a, b), l = max(a, b); - - auto bits_g = g ? int(ceil(log2(g))) : 0; - - // Assume the hash will fit into the output variable - assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); - - return (l << bits_g) + g; - } - - void interconnect_pillars() { - // Now comes the algorithm that connects pillars with each other. - // Ideally every pillar should be connected with at least one of its - // neighbors if that neighbor is within max_pillar_link_distance - - // Pillars with height exceeding H1 will require at least one neighbor - // to connect with. Height exceeding H2 require two neighbors. - double H1 = m_cfg.max_solo_pillar_height_mm; - double H2 = m_cfg.max_dual_pillar_height_mm; - double d = m_cfg.max_pillar_link_distance_mm; - - //A connection between two pillars only counts if the height ratio is - // bigger than 50% - double min_height_ratio = 0.5; - - std::set pairs; - - // A function to connect one pillar with its neighbors. THe number of - // neighbors is given in the configuration. This function if called - // for every pillar in the pillar index. A pair of pillar will not - // be connected multiple times this is ensured by the 'pairs' set which - // remembers the processed pillar pairs - auto cascadefn = - [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) - { - Vec3d qp = el.first; // endpoint of the pillar - - const Pillar& pillar = m_result.pillar(el.second); // actual pillar - - // Get the max number of neighbors a pillar should connect to - unsigned neighbors = m_cfg.pillar_cascade_neighbors; - - // connections are already enough for the pillar - if(pillar.links >= neighbors) return; - - // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ - return distance(e.first, qp) < d; - }); - - // sort the result by distance (have to check if this is needed) - std::sort(qres.begin(), qres.end(), - [qp](const PointIndexEl& e1, const PointIndexEl& e2){ - return distance(e1.first, qp) < distance(e2.first, qp); - }); - - for(auto& re : qres) { // process the queried neighbors - - if(re.second == el.second) continue; // Skip self - - auto a = el.second, b = re.second; - - // Get unique hash for the given pair (order doesn't matter) - auto hashval = pairhash(a, b); - - // Search for the pair amongst the remembered pairs - if(pairs.find(hashval) != pairs.end()) continue; - - const Pillar& neighborpillar = m_result.pillar(re.second); - - // this neighbor is occupied, skip - if(neighborpillar.links >= neighbors) continue; - - if(interconnect(pillar, neighborpillar)) { - pairs.insert(hashval); - - // If the interconnection length between the two pillars is - // less than 50% of the longer pillar's height, don't count - if(pillar.height < H1 || - neighborpillar.height / pillar.height > min_height_ratio) - m_result.increment_links(pillar); - - if(neighborpillar.height < H1 || - pillar.height / neighborpillar.height > min_height_ratio) - m_result.increment_links(neighborpillar); - - } - - // connections are enough for one pillar - if(pillar.links >= neighbors) break; - } - }; - - // Run the cascade for the pillars in the index - m_pillar_index.foreach(cascadefn); - - // We would be done here if we could allow some pillars to not be - // connected with any neighbors. But this might leave the support tree - // unprintable. - // - // The current solution is to insert additional pillars next to these - // lonely pillars. One or even two additional pillar might get inserted - // depending on the length of the lonely pillar. - - size_t pillarcount = m_result.pillarcount(); - - // Again, go through all pillars, this time in the whole support tree - // not just the index. - for(size_t pid = 0; pid < pillarcount; pid++) { - auto pillar = [this, pid]() { return m_result.pillar(pid); }; - - // Decide how many additional pillars will be needed: - - unsigned needpillars = 0; - if (pillar().bridges > m_cfg.max_bridges_on_pillar) - needpillars = 3; - else if (pillar().links < 2 && pillar().height > H2) { - // Not enough neighbors to support this pillar - needpillars = 2 - pillar().links; - } else if (pillar().links < 1 && pillar().height > H1) { - // No neighbors could be found and the pillar is too long. - needpillars = 1; - } - - // Search for new pillar locations: - - bool found = false; - double alpha = 0; // goes to 2Pi - double r = 2 * m_cfg.base_radius_mm; - Vec3d pillarsp = pillar().startpoint(); - - // temp value for starting point detection - Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - - // A vector of bool for placement feasbility - std::vector canplace(needpillars, false); - std::vector spts(needpillars); // vector of starting points - - double gnd = m_result.ground_level; - double min_dist = m_cfg.pillar_base_safety_distance_mm + - m_cfg.base_radius_mm + EPSILON; - - while(!found && alpha < 2*PI) { - for (unsigned n = 0; - n < needpillars && (!n || canplace[n - 1]); - n++) - { - double a = alpha + n * PI / 3; - Vec3d s = sp; - s(X) += std::cos(a) * r; - s(Y) += std::sin(a) * r; - spts[n] = s; - - // Check the path vertically down - auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); - Vec3d gndsp{s(X), s(Y), gnd}; - - // If the path is clear, check for pillar base collisions - canplace[n] = std::isinf(hr.distance()) && - std::sqrt(m_mesh.squared_distance(gndsp)) > - min_dist; - } - - found = std::all_of(canplace.begin(), canplace.end(), - [](bool v) { return v; }); - - // 20 angles will be tried... - alpha += 0.1 * PI; - } - - std::vector newpills; - newpills.reserve(needpillars); - - if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; - Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); - p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); - - if(interconnect(pillar(), p)) { - Pillar& pp = m_result.add_pillar(p); - m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); - - m_result.add_junction(s, pillar().r); - double t = bridge_mesh_intersect(pillarsp, - dirv(pillarsp, s), - pillar().r); - if(distance(pillarsp, s) < t) - m_result.add_bridge(pillarsp, s, pillar().r); - - if(pillar().endpoint()(Z) > m_result.ground_level) - m_result.add_junction(pillar().endpoint(), pillar().r); - - newpills.emplace_back(pp.id); - m_result.increment_links(pillar()); - } - } - - if(!newpills.empty()) { - for(auto it = newpills.begin(), nx = std::next(it); - nx != newpills.end(); ++it, ++nx) { - const Pillar& itpll = m_result.pillar(*it); - const Pillar& nxpll = m_result.pillar(*nx); - if(interconnect(itpll, nxpll)) { - m_result.increment_links(itpll); - m_result.increment_links(nxpll); - } - } - - m_pillar_index.foreach(cascadefn); - } - } - } - - // Step: process the support points where there is not enough space for a - // full pinhead. In this case we will use a rounded sphere as a touching - // point and use a thinner bridge (let's call it a stick). - void routing_headless () - { - // For now we will just generate smaller headless sticks with a sharp - // ending point that connects to the mesh surface. - - // We will sink the pins into the model surface for a distance of 1/3 of - // the pin radius - for(unsigned i : m_iheadless) { m_thr(); - - const auto R = double(m_support_pts[i].head_front_radius); - const double HWIDTH_MM = R/3; - - // Exact support position - Vec3d sph = m_support_pts[i].pos.cast(); - Vec3d n = m_support_nmls.row(i); // mesh outward normal - Vec3d sp = sph - n * HWIDTH_MM; // stick head start point - - Vec3d dir = {0, 0, -1}; - Vec3d sj = sp + R * n; // stick start point - - // This is only for checking - double idist = bridge_mesh_intersect(sph, dir, R, true); - double dist = ray_mesh_intersect(sj, dir); - if (std::isinf(dist)) - dist = sph(Z) - m_mesh.ground_level() - + m_mesh.ground_level_offset(); - - if(std::isnan(idist) || idist < 2*R || - std::isnan(dist) || dist < 2*R) - { - BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" - << " support stick at: " - << sj.transpose(); - continue; - } - - Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); - } - } - - void merge_result() { m_result.merge_and_cleanup(); } -}; - -bool SLASupportTree::generate(const std::vector &support_points, - const EigenMesh3D& mesh, - const SupportConfig &cfg, - const Controller &ctl) -{ - if(support_points.empty()) return false; - - Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); - - // Let's define the individual steps of the processing. We can experiment - // later with the ordering and the dependencies between them. - enum Steps { - BEGIN, - FILTER, - PINHEADS, - CLASSIFY, - ROUTING_GROUND, - ROUTING_NONGROUND, - CASCADE_PILLARS, - HEADLESS, - MERGE_RESULT, - DONE, - ABORT, - NUM_STEPS - //... - }; - - // Collect the algorithm steps into a nice sequence - std::array, NUM_STEPS> program = { - [] () { - // Begin... - // Potentially clear up the shared data (not needed for now) - }, - - std::bind(&Algorithm::filter, &alg), - - std::bind(&Algorithm::add_pinheads, &alg), - - std::bind(&Algorithm::classify, &alg), - - std::bind(&Algorithm::routing_to_ground, &alg), - - std::bind(&Algorithm::routing_to_model, &alg), - - std::bind(&Algorithm::interconnect_pillars, &alg), - - std::bind(&Algorithm::routing_headless, &alg), - - std::bind(&Algorithm::merge_result, &alg), - - [] () { - // Done - }, - - [] () { - // Abort - } - }; - - Steps pc = BEGIN; - - if(cfg.ground_facing_only) { - program[ROUTING_NONGROUND] = []() { - BOOST_LOG_TRIVIAL(info) - << "Skipping model-facing supports as requested."; - }; - program[HEADLESS] = []() { - BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" - " requested."; - }; - } - - // Let's define a simple automaton that will run our program. - auto progress = [&ctl, &pc] () { - static const std::array stepstr { - "Starting", - "Filtering", - "Generate pinheads", - "Classification", - "Routing to ground", - "Routing supports to model surface", - "Interconnecting pillars", - "Processing small holes", - "Merging support mesh", - "Done", - "Abort" - }; - - static const std::array stepstate { - 0, - 10, - 30, - 50, - 60, - 70, - 80, - 85, - 99, - 100, - 0 - }; - - if(ctl.stopcondition()) pc = ABORT; - - switch(pc) { - case BEGIN: pc = FILTER; break; - case FILTER: pc = PINHEADS; break; - case PINHEADS: pc = CLASSIFY; break; - case CLASSIFY: pc = ROUTING_GROUND; break; - case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; - case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; - case CASCADE_PILLARS: pc = HEADLESS; break; - case HEADLESS: pc = MERGE_RESULT; break; - case MERGE_RESULT: pc = DONE; break; - case DONE: - case ABORT: break; - default: ; - } - - ctl.statuscb(stepstate[pc], stepstr[pc]); - }; - - // Just here we run the computation... - while(pc < DONE) { - progress(); - program[pc](); - } - - return pc == ABORT; -} - -SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { - m_impl->ground_level = gnd_lvl; -} - -const TriangleMesh &SLASupportTree::merged_mesh() const -{ - return m_impl->merged_mesh(); -} - -void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { - outmesh.merge(merged_mesh()); - outmesh.merge(get_pad()); -} - -std::vector SLASupportTree::slice( +std::vector SupportTree::slice( const std::vector &grid, float cr) const { - const TriangleMesh &sup_mesh = m_impl->merged_mesh(); - const TriangleMesh &pad_mesh = get_pad(); + const TriangleMesh &sup_mesh = retrieve_mesh(MeshType::Support); + const TriangleMesh &pad_mesh = retrieve_mesh(MeshType::Pad); using Slices = std::vector; auto slices = reserve_vector(2); @@ -2609,7 +61,7 @@ std::vector SLASupportTree::slice( slices.emplace_back(); TriangleMeshSlicer sup_slicer(&sup_mesh); - sup_slicer.slice(grid, cr, &slices.back(), m_impl->ctl().cancelfn); + sup_slicer.slice(grid, cr, &slices.back(), ctl().cancelfn); } if (!pad_mesh.empty()) { @@ -2617,12 +69,13 @@ std::vector SLASupportTree::slice( auto bb = pad_mesh.bounding_box(); auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z()); - - auto padgrid = reserve_vector(grid.end() - maxzit); + + auto cap = grid.end() - maxzit; + auto padgrid = reserve_vector(size_t(cap > 0 ? cap : 0)); std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); TriangleMeshSlicer pad_slicer(&pad_mesh); - pad_slicer.slice(padgrid, cr, &slices.back(), m_impl->ctl().cancelfn); + pad_slicer.slice(padgrid, cr, &slices.back(), ctl().cancelfn); } size_t len = grid.size(); @@ -2644,33 +97,20 @@ std::vector SLASupportTree::slice( return mrg; } -const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, - const PoolConfig& pcfg) const +SupportTree::UPtr SupportTree::create(const SupportableMesh &sm, + const JobController & ctl) { - return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; + auto builder = make_unique(); + builder->m_ctl = ctl; + + if (sm.cfg.enabled) { + builder->build(sm); + builder->merge_and_cleanup(); // clean metadata, leave only the meshes. + } else { + builder->ground_level = sm.emesh.ground_level(); + } + + return std::move(builder); } -const TriangleMesh &SLASupportTree::get_pad() const -{ - return m_impl->pad().tmesh; -} - -void SLASupportTree::remove_pad() -{ - m_impl->remove_pad(); -} - -SLASupportTree::SLASupportTree(const std::vector &points, - const EigenMesh3D& emesh, - const SupportConfig &cfg, - const Controller &ctl): - m_impl(new Impl(ctl)) -{ - m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm; - generate(points, emesh, cfg, ctl); -} - -SLASupportTree::~SLASupportTree() {} - -} -} +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index d7f15c17bd..322b29251e 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -2,24 +2,14 @@ #define SLASUPPORTTREE_HPP #include -#include -#include #include #include #include "SLACommon.hpp" - +#include "SLAPad.hpp" namespace Slic3r { -// Needed types from Point.hpp -typedef int32_t coord_t; -typedef Eigen::Matrix Vec3d; -typedef Eigen::Matrix Vec3f; -typedef Eigen::Matrix Vec3crd; -typedef std::vector Pointf3s; -typedef std::vector Points3; - class TriangleMesh; class Model; class ModelInstance; @@ -32,13 +22,17 @@ using ExPolygons = std::vector; namespace sla { -enum class PillarConnectionMode { +enum class PillarConnectionMode +{ zigzag, cross, dynamic }; -struct SupportConfig { +struct SupportConfig +{ + bool enabled = true; + // Radius in mm of the pointing side of the head. double head_front_radius_mm = 0.2; @@ -85,6 +79,11 @@ struct SupportConfig { // The shortest distance between a pillar base perimeter from the model // body. This is only useful when elevation is set to zero. double pillar_base_safety_distance_mm = 0.5; + + double head_fullwidth() const { + return 2 * head_front_radius_mm + head_width_mm + + 2 * head_back_radius_mm - head_penetration_mm; + } // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) @@ -104,101 +103,78 @@ struct SupportConfig { static const unsigned max_bridges_on_pillar; }; -struct PoolConfig; +enum class MeshType { Support, Pad }; /// A Control structure for the support calculation. Consists of the status /// indicator callback and the stop condition predicate. -struct Controller { - +struct JobController +{ + using StatusFn = std::function; + using StopCond = std::function; + using CancelFn = std::function; + // This will signal the status of the calculation to the front-end - std::function statuscb = - [](unsigned, const std::string&){}; - + StatusFn statuscb = [](unsigned, const std::string&){}; + // Returns true if the calculation should be aborted. - std::function stopcondition = [](){ return false; }; - + 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 - std::function cancelfn = [](){}; + CancelFn cancelfn = [](){}; }; -using PointSet = Eigen::MatrixXd; +struct SupportableMesh +{ + EigenMesh3D emesh; + SupportPoints pts; + SupportConfig cfg; -//EigenMesh3D to_eigenmesh(const TriangleMesh& m); - -// needed for find best rotation -//EigenMesh3D to_eigenmesh(const ModelObject& model); - -// Simple conversion of 'vector of points' to an Eigen matrix -//PointSet to_point_set(const std::vector&); - - -/* ************************************************************************** */ + explicit SupportableMesh(const TriangleMesh & trmsh, + const SupportPoints &sp, + const SupportConfig &c) + : emesh{trmsh}, pts{sp}, cfg{c} + {} + + explicit SupportableMesh(const EigenMesh3D &em, + const SupportPoints &sp, + const SupportConfig &c) + : emesh{em}, pts{sp}, cfg{c} + {} +}; /// The class containing mesh data for the generated supports. -class SLASupportTree { - class Impl; // persistent support data - std::unique_ptr m_impl; - - Impl& get() { return *m_impl; } - const Impl& get() const { return *m_impl; } - - friend void add_sla_supports(Model&, - const SupportConfig&, - const Controller&); - - // The generation algorithm is quite long and will be captured in a separate - // class with private data, helper methods, etc... This data is only needed - // during the calculation whereas the Impl class contains the persistent - // data, mostly the meshes. - class Algorithm; - - // Generate the 3D supports for a model intended for SLA print. This - // will instantiate the Algorithm class and call its appropriate methods - // with status indication. - bool generate(const std::vector& pts, - const EigenMesh3D& mesh, - const SupportConfig& cfg = {}, - const Controller& ctl = {}); - +class SupportTree +{ + JobController m_ctl; public: - - SLASupportTree(double ground_level = 0.0); - - SLASupportTree(const std::vector& pts, - const EigenMesh3D& em, - const SupportConfig& cfg = {}, - const Controller& ctl = {}); + using UPtr = std::unique_ptr; - SLASupportTree(const SLASupportTree&) = delete; - SLASupportTree& operator=(const SLASupportTree&) = delete; + static UPtr create(const SupportableMesh &input, + const JobController &ctl = {}); - ~SLASupportTree(); + virtual ~SupportTree() = default; - /// Get the whole mesh united into the output TriangleMesh - /// WITHOUT THE PAD - const TriangleMesh& merged_mesh() const; + virtual const TriangleMesh &retrieve_mesh(MeshType meshtype) const = 0; - void merged_mesh_with_pad(TriangleMesh&) const; - - std::vector slice(const std::vector &, - float closing_radius) const; - - /// Adding the "pad" (base pool) under the supports + /// Adding the "pad" under the supports. /// modelbase will be used according to the embed_object flag in PoolConfig. - /// If set, the plate will interpreted as the model's intrinsic pad. + /// If set, the plate will be interpreted as the model's intrinsic pad. /// Otherwise, the modelbase will be unified with the base plate calculated /// from the supports. - const TriangleMesh& add_pad(const ExPolygons& modelbase, - const PoolConfig& pcfg) const; - - /// Get the pad geometry - const TriangleMesh& get_pad() const; - - void remove_pad(); - + virtual const TriangleMesh &add_pad(const ExPolygons &modelbase, + const PadConfig & pcfg) = 0; + + virtual void remove_pad() = 0; + + std::vector slice(const std::vector &, + float closing_radius) const; + + void retrieve_full_mesh(TriangleMesh &outmesh) const; + + const JobController &ctl() const { return m_ctl; } }; } diff --git a/src/libslic3r/SLA/SLASupportTreeBuilder.cpp b/src/libslic3r/SLA/SLASupportTreeBuilder.cpp new file mode 100644 index 0000000000..2e0310ed8d --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTreeBuilder.cpp @@ -0,0 +1,525 @@ +#include "SLASupportTreeBuilder.hpp" +#include "SLASupportTreeBuildsteps.hpp" + +namespace Slic3r { +namespace sla { + +Contour3D sphere(double rho, Portion portion, double fa) { + + Contour3D ret; + + // prohibit close to zero radius + if(rho <= 1e-6 && rho >= -1e-6) return ret; + + auto& vertices = ret.points; + auto& facets = ret.indices; + + // Algorithm: + // Add points one-by-one to the sphere grid and form facets using relative + // coordinates. Sphere is composed effectively of a mesh of stacked circles. + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // Ring to be scaled to generate the steps of the sphere + std::vector ring; + + for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); + + const auto sbegin = size_t(2*std::get<0>(portion)/angle); + const auto send = size_t(2*std::get<1>(portion)/angle); + + const size_t steps = ring.size(); + const double increment = 1.0 / double(steps); + + // special case: first ring connects to 0,0,0 + // insert and form facets. + if(sbegin == 0) + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); + + auto id = coord_t(vertices.size()); + for (size_t i = 0; i < ring.size(); i++) { + // Fixed scaling + const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); + // radius of the circle for this step. + const double r = std::sqrt(std::abs(rho*rho - z*z)); + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + + if (sbegin == 0) + facets.emplace_back((i == 0) ? + Vec3crd(coord_t(ring.size()), 0, 1) : + Vec3crd(id - 1, 0, id)); + ++id; + } + + // General case: insert and form facets for each step, + // joining it to the ring below it. + for (size_t s = sbegin + 2; s < send - 1; s++) { + const double z = -rho + increment*double(s*2.0*rho); + const double r = std::sqrt(std::abs(rho*rho - z*z)); + + for (size_t i = 0; i < ring.size(); i++) { + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // wrap around + facets.emplace_back(Vec3crd(id - 1, id, + id + coord_t(ring.size() - 1))); + facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); + } else { + facets.emplace_back(Vec3crd(id_ringsize - 1, id_ringsize, id)); + facets.emplace_back(Vec3crd(id - 1, id_ringsize - 1, id)); + } + id++; + } + } + + // special case: last ring connects to 0,0,rho*2.0 + // only form facets. + if(send >= size_t(2*PI / angle)) { + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); + for (size_t i = 0; i < ring.size(); i++) { + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // third vertex is on the other side of the ring. + facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); + } else { + auto ci = coord_t(id_ringsize + coord_t(i)); + facets.emplace_back(Vec3crd(ci - 1, ci, id)); + } + } + } + id++; + + return ret; +} + +Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) +{ + Contour3D ret; + + auto steps = int(ssteps); + auto& points = ret.points; + auto& indices = ret.indices; + points.reserve(2*ssteps); + double a = 2*PI/steps; + + Vec3d jp = sp; + Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; + + // Upper circle points + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double ex = endp(X) + r*std::cos(phi); + double ey = endp(Y) + r*std::sin(phi); + points.emplace_back(ex, ey, endp(Z)); + } + + // Lower circle points + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double x = jp(X) + r*std::cos(phi); + double y = jp(Y) + r*std::sin(phi); + points.emplace_back(x, y, jp(Z)); + } + + // Now create long triangles connecting upper and lower circles + indices.reserve(2*ssteps); + auto offs = steps; + for(int i = 0; i < steps - 1; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + } + + // Last triangle connecting the first and last vertices + auto last = steps - 1; + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + + // According to the slicing algorithms, we need to aid them with generating + // a watertight body. So we create a triangle fan for the upper and lower + // ending of the cylinder to close the geometry. + points.emplace_back(jp); int ci = int(points.size() - 1); + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(i + offs + 1, i + offs, ci); + + indices.emplace_back(offs, steps + offs - 1, ci); + + points.emplace_back(endp); ci = int(points.size() - 1); + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(ci, i, i + 1); + + indices.emplace_back(steps - 1, 0, ci); + + return ret; +} + +Head::Head(double r_big_mm, + double r_small_mm, + double length_mm, + double penetration, + const Vec3d &direction, + const Vec3d &offset, + const size_t circlesteps) + : steps(circlesteps) + , dir(direction) + , tr(offset) + , r_back_mm(r_big_mm) + , r_pin_mm(r_small_mm) + , width_mm(length_mm) + , penetration_mm(penetration) +{ + assert(width_mm > 0.); + assert(r_back_mm > 0.); + assert(r_pin_mm > 0.); + + // We create two spheres which will be connected with a robe that fits + // both circles perfectly. + + // Set up the model detail level + const double detail = 2*PI/steps; + + // We don't generate whole circles. Instead, we generate only the + // portions which are visible (not covered by the robe) To know the + // exact portion of the bottom and top circles we need to use some + // rules of tangent circles from which we can derive (using simple + // triangles the following relations: + + // The height of the whole mesh + const double h = r_big_mm + r_small_mm + width_mm; + double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); + + // To generate a whole circle we would pass a portion of (0, Pi) + // To generate only a half horizontal circle we can pass (0, Pi/2) + // The calculated phi is an offset to the half circles needed to smooth + // the transition from the circle to the robe geometry + + auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); + auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); + + for(auto& p : s2.points) p.z() += h; + + mesh.merge(s1); + mesh.merge(s2); + + for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; + idx1++, idx2++) + { + coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); + coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; + + mesh.indices.emplace_back(i1s1, i2s1, i2s2); + mesh.indices.emplace_back(i1s1, i2s2, i1s2); + } + + auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); + auto i2s1 = coord_t(s1.points.size()) - 1; + auto i1s2 = coord_t(s1.points.size()); + auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; + + mesh.indices.emplace_back(i2s2, i2s1, i1s1); + mesh.indices.emplace_back(i1s2, i2s2, i1s1); + + // 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) + for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm); +} + +Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): + r(radius), steps(st), endpt(endp), starts_from_head(false) +{ + assert(steps > 0); + + height = jp(Z) - endp(Z); + if(height > EPSILON) { // Endpoint is below the starting point + + // We just create a bridge geometry with the pillar parameters and + // move the data. + Contour3D body = cylinder(radius, height, st, endp); + mesh.points.swap(body.points); + mesh.indices.swap(body.indices); + } +} + +Pillar &Pillar::add_base(double baseheight, double radius) +{ + if(baseheight <= 0) return *this; + if(baseheight > height) baseheight = height; + + assert(steps >= 0); + auto last = int(steps - 1); + + if(radius < r ) radius = r; + + double a = 2*PI/steps; + double z = endpt(Z) + baseheight; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + r*std::cos(phi); + double y = endpt(Y) + r*std::sin(phi); + base.points.emplace_back(x, y, z); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - baseheight); + } + + auto ep = endpt; ep(Z) += baseheight; + base.points.emplace_back(endpt); + base.points.emplace_back(ep); + + auto& indices = base.indices; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for(int i = 0; i < last; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + indices.emplace_back(i, i + 1, hcenter); + indices.emplace_back(lcenter, offs + i + 1, offs + i); + } + + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + indices.emplace_back(hcenter, last, 0); + indices.emplace_back(offs, offs + last, lcenter); + return *this; +} + +Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): + r(r_mm), startp(j1), endp(j2) +{ + using Quaternion = Eigen::Quaternion; + Vec3d dir = (j2 - j1).normalized(); + double d = distance(j2, j1); + + mesh = cylinder(r, d, steps); + + auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); + for(auto& p : mesh.points) p = quater * p + j1; +} + +CompactBridge::CompactBridge(const Vec3d &sp, + const Vec3d &ep, + const Vec3d &n, + double r, + bool endball, + size_t steps) +{ + Vec3d startp = sp + r * n; + Vec3d dir = (ep - startp).normalized(); + Vec3d endp = ep - r * dir; + + Bridge br(startp, endp, r, steps); + mesh.merge(br.mesh); + + // now add the pins + double fa = 2*PI/steps; + auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); + for(auto& p : upperball.points) p += startp; + + if(endball) { + auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); + for(auto& p : lowerball.points) p += endp; + mesh.merge(lowerball); + } + + mesh.merge(upperball); +} + +Pad::Pad(const TriangleMesh &support_mesh, + const ExPolygons & model_contours, + double ground_level, + const PadConfig & pcfg, + ThrowOnCancel thr) + : cfg(pcfg) + , zlevel(ground_level + pcfg.full_height() - pcfg.required_elevation()) +{ + thr(); + + ExPolygons sup_contours; + + float zstart = float(zlevel); + float zend = zstart + float(pcfg.full_height() + EPSILON); + + pad_blueprint(support_mesh, sup_contours, grid(zstart, zend, 0.1f), thr); + create_pad(sup_contours, model_contours, tmesh, pcfg); + + tmesh.translate(0, 0, float(zlevel)); + if (!tmesh.empty()) tmesh.require_shared_vertices(); +} + +const TriangleMesh &SupportTreeBuilder::add_pad(const ExPolygons &modelbase, + const PadConfig & cfg) +{ + m_pad = Pad{merged_mesh(), modelbase, ground_level, cfg, ctl().cancelfn}; + return m_pad.tmesh; +} + +SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o) + : m_heads(std::move(o.m_heads)) + , m_head_indices{std::move(o.m_head_indices)} + , m_pillars{std::move(o.m_pillars)} + , m_bridges{std::move(o.m_bridges)} + , m_crossbridges{std::move(o.m_crossbridges)} + , m_compact_bridges{std::move(o.m_compact_bridges)} + , m_pad{std::move(o.m_pad)} + , m_meshcache{std::move(o.m_meshcache)} + , m_meshcache_valid{o.m_meshcache_valid} + , m_model_height{o.m_model_height} + , ground_level{o.ground_level} +{} + +SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o) + : m_heads(o.m_heads) + , m_head_indices{o.m_head_indices} + , m_pillars{o.m_pillars} + , m_bridges{o.m_bridges} + , m_crossbridges{o.m_crossbridges} + , m_compact_bridges{o.m_compact_bridges} + , m_pad{o.m_pad} + , m_meshcache{o.m_meshcache} + , m_meshcache_valid{o.m_meshcache_valid} + , m_model_height{o.m_model_height} + , ground_level{o.ground_level} +{} + +SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o) +{ + m_heads = std::move(o.m_heads); + m_head_indices = std::move(o.m_head_indices); + m_pillars = std::move(o.m_pillars); + m_bridges = std::move(o.m_bridges); + m_crossbridges = std::move(o.m_crossbridges); + m_compact_bridges = std::move(o.m_compact_bridges); + m_pad = std::move(o.m_pad); + m_meshcache = std::move(o.m_meshcache); + m_meshcache_valid = o.m_meshcache_valid; + m_model_height = o.m_model_height; + ground_level = o.ground_level; + return *this; +} + +SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) +{ + m_heads = o.m_heads; + m_head_indices = o.m_head_indices; + m_pillars = o.m_pillars; + m_bridges = o.m_bridges; + m_crossbridges = o.m_crossbridges; + m_compact_bridges = o.m_compact_bridges; + m_pad = o.m_pad; + m_meshcache = o.m_meshcache; + m_meshcache_valid = o.m_meshcache_valid; + m_model_height = o.m_model_height; + ground_level = o.ground_level; + return *this; +} + +const TriangleMesh &SupportTreeBuilder::merged_mesh() const +{ + if (m_meshcache_valid) return m_meshcache; + + Contour3D merged; + + for (auto &head : m_heads) { + if (ctl().stopcondition()) break; + if (head.is_valid()) merged.merge(head.mesh); + } + + for (auto &stick : m_pillars) { + if (ctl().stopcondition()) break; + merged.merge(stick.mesh); + merged.merge(stick.base); + } + + for (auto &j : m_junctions) { + if (ctl().stopcondition()) break; + merged.merge(j.mesh); + } + + for (auto &cb : m_compact_bridges) { + if (ctl().stopcondition()) break; + merged.merge(cb.mesh); + } + + for (auto &bs : m_bridges) { + if (ctl().stopcondition()) break; + merged.merge(bs.mesh); + } + + for (auto &bs : m_crossbridges) { + if (ctl().stopcondition()) break; + merged.merge(bs.mesh); + } + + if (ctl().stopcondition()) { + // In case of failure we have to return an empty mesh + m_meshcache = TriangleMesh(); + return m_meshcache; + } + + m_meshcache = mesh(merged); + + // The mesh will be passed by const-pointer to TriangleMeshSlicer, + // which will need this. + if (!m_meshcache.empty()) m_meshcache.require_shared_vertices(); + + BoundingBoxf3 &&bb = m_meshcache.bounding_box(); + m_model_height = bb.max(Z) - bb.min(Z); + + m_meshcache_valid = true; + return m_meshcache; +} + +double SupportTreeBuilder::full_height() const +{ + if (merged_mesh().empty() && !pad().empty()) + return pad().cfg.full_height(); + + double h = mesh_height(); + if (!pad().empty()) h += pad().cfg.required_elevation(); + return h; +} + +const TriangleMesh &SupportTreeBuilder::merge_and_cleanup() +{ + // in case the mesh is not generated, it should be... + auto &ret = merged_mesh(); + + // Doing clear() does not garantee to release the memory. + m_heads = {}; + m_head_indices = {}; + m_pillars = {}; + m_junctions = {}; + m_bridges = {}; + m_compact_bridges = {}; + + return ret; +} + +const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const +{ + switch(meshtype) { + case MeshType::Support: return merged_mesh(); + case MeshType::Pad: return pad().tmesh; + } + + return m_meshcache; +} + +bool SupportTreeBuilder::build(const SupportableMesh &sm) +{ + ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; + return SupportTreeBuildsteps::execute(*this, sm); +} + +} +} diff --git a/src/libslic3r/SLA/SLASupportTreeBuilder.hpp b/src/libslic3r/SLA/SLASupportTreeBuilder.hpp new file mode 100644 index 0000000000..c0d9f04c0f --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTreeBuilder.hpp @@ -0,0 +1,496 @@ +#ifndef SUPPORTTREEBUILDER_HPP +#define SUPPORTTREEBUILDER_HPP + +#include "SLAConcurrency.hpp" +#include "SLABoilerPlate.hpp" +#include "SLASupportTree.hpp" +#include "SLAPad.hpp" +#include + +namespace Slic3r { +namespace sla { + +/** + * Terminology: + * + * Support point: + * The point on the model surface that needs support. + * + * Pillar: + * A thick column that spans from a support point to the ground and has + * a thick cone shaped base where it touches the ground. + * + * Ground facing support point: + * A support point that can be directly connected with the ground with a pillar + * that does not collide or cut through the model. + * + * Non ground facing support point: + * A support point that cannot be directly connected with the ground (only with + * the model surface). + * + * Head: + * The pinhead that connects to the model surface with the sharp end end + * to a pillar or bridge stick with the dull end. + * + * Headless support point: + * A support point on the model surface for which there is not enough place for + * the head. It is either in a hole or there is some barrier that would collide + * with the head geometry. The headless support point can be ground facing and + * non ground facing as well. + * + * Bridge: + * A stick that connects two pillars or a head with a pillar. + * + * Junction: + * A small ball in the intersection of two or more sticks (pillar, bridge, ...) + * + * CompactBridge: + * A bridge that connects a headless support point with the model surface or a + * nearby pillar. + */ + +using Coordf = double; +using Portion = std::tuple; + +inline Portion make_portion(double a, double b) { + return std::make_tuple(a, b); +} + +template double distance(const Vec& p) { + return std::sqrt(p.transpose() * p); +} + +template double distance(const Vec& pp1, const Vec& pp2) { + auto p = pp2 - pp1; + return distance(p); +} + +Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), + double fa=(2*PI/360)); + +// Down facing cylinder in Z direction with arguments: +// r: radius +// h: Height +// ssteps: how many edges will create the base circle +// sp: starting point +Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0}); + +const constexpr long ID_UNSET = -1; + +struct Head { + Contour3D mesh; + + size_t steps = 45; + Vec3d dir = {0, 0, -1}; + Vec3d tr = {0, 0, 0}; + + double r_back_mm = 1; + double r_pin_mm = 0.5; + double width_mm = 2; + double penetration_mm = 0.5; + + // For identification purposes. This will be used as the index into the + // container holding the head structures. See SLASupportTree::Impl + long id = ID_UNSET; + + // If there is a pillar connecting to this head, then the id will be set. + long pillar_id = ID_UNSET; + + long bridge_id = ID_UNSET; + + inline void invalidate() { id = ID_UNSET; } + inline bool is_valid() const { return id >= 0; } + + Head(double r_big_mm, + double r_small_mm, + double length_mm, + double penetration, + const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end) + const Vec3d &offset = {0, 0, 0}, // displacement + const size_t circlesteps = 45); + + void transform() + { + using Quaternion = Eigen::Quaternion; + + // We rotate the head to the specified direction The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); + + for(auto& p : mesh.points) p = quatern * p + tr; + } + + inline double fullwidth() const + { + return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; + } + + inline Vec3d junction_point() const + { + return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; + } + + inline double request_pillar_radius(double radius) const + { + const double rmax = r_back_mm; + return radius > 0 && radius < rmax ? radius : rmax; + } +}; + +struct Junction { + Contour3D mesh; + double r = 1; + size_t steps = 45; + Vec3d pos; + + long id = ID_UNSET; + + Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): + r(r_mm), steps(stepnum), pos(tr) + { + mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); + for(auto& p : mesh.points) p += tr; + } +}; + +struct Pillar { + Contour3D mesh; + Contour3D base; + double r = 1; + size_t steps = 0; + Vec3d endpt; + double height = 0; + + long id = ID_UNSET; + + // If the pillar connects to a head, this is the id of that head + bool starts_from_head = true; // Could start from a junction as well + long start_junction_id = ID_UNSET; + + // How many bridges are connected to this pillar + unsigned bridges = 0; + + // How many pillars are cascaded with this one + unsigned links = 0; + + Pillar(const Vec3d& jp, const Vec3d& endp, + double radius = 1, size_t st = 45); + + Pillar(const Junction &junc, const Vec3d &endp) + : Pillar(junc.pos, endp, junc.r, junc.steps) + {} + + Pillar(const Head &head, const Vec3d &endp, double radius = 1) + : Pillar(head.junction_point(), endp, + head.request_pillar_radius(radius), head.steps) + {} + + inline Vec3d startpoint() const + { + return {endpt(X), endpt(Y), endpt(Z) + height}; + } + + inline const Vec3d& endpoint() const { return endpt; } + + Pillar& add_base(double baseheight = 3, double radius = 2); +}; + +// A Bridge between two pillars (with junction endpoints) +struct Bridge { + Contour3D mesh; + double r = 0.8; + long id = ID_UNSET; + Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); + + Bridge(const Vec3d &j1, + const Vec3d &j2, + double r_mm = 0.8, + size_t steps = 45); +}; + +// A bridge that spans from model surface to model surface with small connecting +// edges on the endpoints. Used for headless support points. +struct CompactBridge { + Contour3D mesh; + long id = ID_UNSET; + + CompactBridge(const Vec3d& sp, + const Vec3d& ep, + const Vec3d& n, + double r, + bool endball = true, + size_t steps = 45); +}; + +// A wrapper struct around the pad +struct Pad { + TriangleMesh tmesh; + PadConfig cfg; + double zlevel = 0; + + Pad() = default; + + Pad(const TriangleMesh &support_mesh, + const ExPolygons & model_contours, + double ground_level, + const PadConfig & pcfg, + ThrowOnCancel thr); + + bool empty() const { return tmesh.facets_count() == 0; } +}; + +// This class will hold the support tree meshes with some additional +// bookkeeping as well. Various parts of the support geometry are stored +// separately and are merged when the caller queries the merged mesh. The +// merged result is cached for fast subsequent delivery of the merged mesh +// which can be quite complex. The support tree creation algorithm can use an +// instance of this class as a somewhat higher level tool for crafting the 3D +// support mesh. Parts can be added with the appropriate methods such as +// add_head or add_pillar which forwards the constructor arguments and fills +// the IDs of these substructures. The IDs are basically indices into the +// arrays of the appropriate type (heads, pillars, etc...). One can later query +// e.g. a pillar for a specific head... +// +// The support pad is considered an auxiliary geometry and is not part of the +// merged mesh. It can be retrieved using a dedicated method (pad()) +class SupportTreeBuilder: public SupportTree { + // For heads it is beneficial to use the same IDs as for the support points. + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; + std::vector m_junctions; + std::vector m_bridges; + std::vector m_crossbridges; + std::vector m_compact_bridges; + Pad m_pad; + + using Mutex = ccr::SpinningMutex; + + mutable TriangleMesh m_meshcache; + mutable Mutex m_mutex; + mutable bool m_meshcache_valid = false; + mutable double m_model_height = 0; // the full height of the model + + template + const Bridge& _add_bridge(std::vector &br, Args&&... args) + { + std::lock_guard lk(m_mutex); + br.emplace_back(std::forward(args)...); + br.back().id = long(br.size() - 1); + m_meshcache_valid = false; + return br.back(); + } + +public: + double ground_level = 0; + + SupportTreeBuilder() = default; + SupportTreeBuilder(SupportTreeBuilder &&o); + SupportTreeBuilder(const SupportTreeBuilder &o); + SupportTreeBuilder& operator=(SupportTreeBuilder &&o); + SupportTreeBuilder& operator=(const SupportTreeBuilder &o); + + template Head& add_head(unsigned id, Args&&... args) + { + std::lock_guard lk(m_mutex); + m_heads.emplace_back(std::forward(args)...); + m_heads.back().id = id; + + if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); + m_head_indices[id] = m_heads.size() - 1; + + m_meshcache_valid = false; + return m_heads.back(); + } + + template long add_pillar(long headid, Args&&... args) + { + std::lock_guard lk(m_mutex); + if (m_pillars.capacity() < m_heads.size()) + m_pillars.reserve(m_heads.size() * 10); + + assert(headid >= 0 && size_t(headid) < m_head_indices.size()); + Head &head = m_heads[m_head_indices[size_t(headid)]]; + + m_pillars.emplace_back(head, std::forward(args)...); + Pillar& pillar = m_pillars.back(); + pillar.id = long(m_pillars.size() - 1); + head.pillar_id = pillar.id; + pillar.start_junction_id = head.id; + pillar.starts_from_head = true; + + m_meshcache_valid = false; + return pillar.id; + } + + void add_pillar_base(long pid, double baseheight = 3, double radius = 2) + { + std::lock_guard lk(m_mutex); + assert(pid >= 0 && size_t(pid) < m_pillars.size()); + m_pillars[size_t(pid)].add_base(baseheight, radius); + } + + void increment_bridges(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + m_pillars[size_t(pillar.id)].bridges++; + } + + void increment_links(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + m_pillars[size_t(pillar.id)].links++; + } + + unsigned bridgecount(const Pillar &pillar) const { + std::lock_guard lk(m_mutex); + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + return pillar.bridges; + } + + template long add_pillar(Args&&...args) + { + std::lock_guard lk(m_mutex); + if (m_pillars.capacity() < m_heads.size()) + m_pillars.reserve(m_heads.size() * 10); + + m_pillars.emplace_back(std::forward(args)...); + Pillar& pillar = m_pillars.back(); + pillar.id = long(m_pillars.size() - 1); + pillar.starts_from_head = false; + m_meshcache_valid = false; + return pillar.id; + } + + const Pillar& head_pillar(unsigned headid) const + { + std::lock_guard lk(m_mutex); + assert(headid < m_head_indices.size()); + + const Head& h = m_heads[m_head_indices[headid]]; + assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); + + return m_pillars[size_t(h.pillar_id)]; + } + + template const Junction& add_junction(Args&&... args) + { + std::lock_guard lk(m_mutex); + m_junctions.emplace_back(std::forward(args)...); + m_junctions.back().id = long(m_junctions.size() - 1); + m_meshcache_valid = false; + return m_junctions.back(); + } + + const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45) + { + return _add_bridge(m_bridges, s, e, r, n); + } + + const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45) + { + std::lock_guard lk(m_mutex); + assert(headid >= 0 && size_t(headid) < m_head_indices.size()); + + Head &h = m_heads[m_head_indices[size_t(headid)]]; + m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s); + m_bridges.back().id = long(m_bridges.size() - 1); + + h.bridge_id = m_bridges.back().id; + m_meshcache_valid = false; + return m_bridges.back(); + } + + template const Bridge& add_crossbridge(Args&&... args) + { + return _add_bridge(m_crossbridges, std::forward(args)...); + } + + template const CompactBridge& add_compact_bridge(Args&&...args) + { + std::lock_guard lk(m_mutex); + m_compact_bridges.emplace_back(std::forward(args)...); + m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); + m_meshcache_valid = false; + return m_compact_bridges.back(); + } + + Head &head(unsigned id) + { + std::lock_guard lk(m_mutex); + assert(id < m_head_indices.size()); + + m_meshcache_valid = false; + return m_heads[m_head_indices[id]]; + } + + inline size_t pillarcount() const { + std::lock_guard lk(m_mutex); + return m_pillars.size(); + } + + inline const std::vector &pillars() const { return m_pillars; } + inline const std::vector &heads() const { return m_heads; } + inline const std::vector &bridges() const { return m_bridges; } + inline const std::vector &crossbridges() const { return m_crossbridges; } + + template inline IntegerOnly pillar(T id) const + { + std::lock_guard lk(m_mutex); + assert(id >= 0 && size_t(id) < m_pillars.size() && + size_t(id) < std::numeric_limits::max()); + + return m_pillars[size_t(id)]; + } + + template inline IntegerOnly pillar(T id) + { + std::lock_guard lk(m_mutex); + assert(id >= 0 && size_t(id) < m_pillars.size() && + size_t(id) < std::numeric_limits::max()); + + return m_pillars[size_t(id)]; + } + + const Pad& pad() const { return m_pad; } + + // WITHOUT THE PAD!!! + const TriangleMesh &merged_mesh() const; + + // WITH THE PAD + double full_height() const; + + // WITHOUT THE PAD!!! + inline double mesh_height() const + { + if (!m_meshcache_valid) merged_mesh(); + return m_model_height; + } + + // Intended to be called after the generation is fully complete + const TriangleMesh & merge_and_cleanup(); + + // Implement SupportTree interface: + + const TriangleMesh &add_pad(const ExPolygons &modelbase, + const PadConfig & pcfg) override; + + void remove_pad() override { m_pad = Pad(); } + + virtual const TriangleMesh &retrieve_mesh( + MeshType meshtype = MeshType::Support) const override; + + bool build(const SupportableMesh &supportable_mesh); +}; + +}} // namespace Slic3r::sla + +#endif // SUPPORTTREEBUILDER_HPP diff --git a/src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp new file mode 100644 index 0000000000..392a963e12 --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp @@ -0,0 +1,1387 @@ +#include "SLASupportTreeBuildsteps.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace sla { + +SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, + const SupportableMesh &sm) + : m_cfg(sm.cfg) + , m_mesh(sm.emesh) + , m_support_pts(sm.pts) + , m_support_nmls(sm.pts.size(), 3) + , m_builder(builder) + , m_points(sm.pts.size(), 3) + , m_thr(builder.ctl().cancelfn) +{ + // Prepare the support points in Eigen/IGL format as well, we will use + // it mostly in this form. + + long i = 0; + for (const SupportPoint &sp : m_support_pts) { + m_points.row(i)(X) = double(sp.pos(X)); + m_points.row(i)(Y) = double(sp.pos(Y)); + m_points.row(i)(Z) = double(sp.pos(Z)); + ++i; + } +} + +bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, + const SupportableMesh &sm) +{ + if(sm.pts.empty()) return false; + + SupportTreeBuildsteps alg(builder, sm); + + // Let's define the individual steps of the processing. We can experiment + // later with the ordering and the dependencies between them. + enum Steps { + BEGIN, + FILTER, + PINHEADS, + CLASSIFY, + ROUTING_GROUND, + ROUTING_NONGROUND, + CASCADE_PILLARS, + HEADLESS, + MERGE_RESULT, + DONE, + ABORT, + NUM_STEPS + //... + }; + + // Collect the algorithm steps into a nice sequence + std::array, NUM_STEPS> program = { + [] () { + // Begin... + // Potentially clear up the shared data (not needed for now) + }, + + std::bind(&SupportTreeBuildsteps::filter, &alg), + + std::bind(&SupportTreeBuildsteps::add_pinheads, &alg), + + std::bind(&SupportTreeBuildsteps::classify, &alg), + + std::bind(&SupportTreeBuildsteps::routing_to_ground, &alg), + + std::bind(&SupportTreeBuildsteps::routing_to_model, &alg), + + std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg), + + std::bind(&SupportTreeBuildsteps::routing_headless, &alg), + + std::bind(&SupportTreeBuildsteps::merge_result, &alg), + + [] () { + // Done + }, + + [] () { + // Abort + } + }; + + Steps pc = BEGIN; + + if(sm.cfg.ground_facing_only) { + program[ROUTING_NONGROUND] = []() { + BOOST_LOG_TRIVIAL(info) + << "Skipping model-facing supports as requested."; + }; + program[HEADLESS] = []() { + BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" + " requested."; + }; + } + + // Let's define a simple automaton that will run our program. + auto progress = [&builder, &pc] () { + static const std::array stepstr { + "Starting", + "Filtering", + "Generate pinheads", + "Classification", + "Routing to ground", + "Routing supports to model surface", + "Interconnecting pillars", + "Processing small holes", + "Merging support mesh", + "Done", + "Abort" + }; + + static const std::array stepstate { + 0, + 10, + 30, + 50, + 60, + 70, + 80, + 85, + 99, + 100, + 0 + }; + + if(builder.ctl().stopcondition()) pc = ABORT; + + switch(pc) { + case BEGIN: pc = FILTER; break; + case FILTER: pc = PINHEADS; break; + case PINHEADS: pc = CLASSIFY; break; + case CLASSIFY: pc = ROUTING_GROUND; break; + case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; + case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; + case CASCADE_PILLARS: pc = HEADLESS; break; + case HEADLESS: pc = MERGE_RESULT; break; + case MERGE_RESULT: pc = DONE; break; + case DONE: + case ABORT: break; + default: ; + } + + builder.ctl().statuscb(stepstate[pc], stepstr[pc]); + }; + + // Just here we run the computation... + while(pc < DONE) { + progress(); + program[pc](); + } + + return pc == ABORT; +} + +EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( + const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) +{ + static const size_t SAMPLES = 8; + + // method based on: + // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + Vec3d v = dir; // Our direction (axis) + Vec3d c = s + width * dir; + const double& sd = m_cfg.safety_distance_mm; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + Vec3d a(0, 1, 0), b; + + // The portions of the circle (the head-back circle) for which we will + // shoot rays. + std::array phis; + for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + + auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + auto chk1 = [] (double val) { + return std::abs(std::abs(val) - 1) < 1e-20; + }; + + if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { + a = {v(Z), v(X), v(Y)}; + b = {v(Y), v(Z), v(X)}; + } + else { + a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); + b = a.cross(v); + } + + // Now a and b vectors are perpendicular to v and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'phis' vector + ccr::enumerate( + phis.begin(), phis.end(), + [&hits, &m, sd, r_pin, r_back, s, a, b, c](double phi, size_t i) { + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + // Let's have a safety coefficient for the radiuses. + double rpscos = (sd + r_pin) * cosphi; + double rpssin = (sd + r_pin) * sinphi; + double rpbcos = (sd + r_back) * cosphi; + double rpbsin = (sd + r_back) * sinphi; + + // Point on the circle on the pin sphere + Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), + s(Y) + rpscos * a(Y) + rpssin * b(Y), + s(Z) + rpscos * a(Z) + rpssin * b(Z)); + + // Point ps is not on mesh but can be inside or + // outside as well. This would cause many problems + // with ray-casting. To detect the position we will + // use the ray-casting result (which has an is_inside + // predicate). + + // This is the point on the circle on the back sphere + Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), + c(Y) + rpbcos * a(Y) + rpbsin * b(Y), + c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd * n, n); + + if (q.is_inside()) { // the hit is inside the model + if (q.distance() > r_pin + sd) { + // If we are inside the model and the hit + // distance is bigger than our pin circle + // diameter, it probably indicates that the + // support point was already inside the + // model, or there is really no space + // around the point. We will assign a zero + // hit distance to these cases which will + // enforce the function return value to be + // an invalid ray with zero hit distance. + // (see min_element at the end) + hits[i] = HitResult(0.0); + } else { + // re-cast the ray from the outside of the + // object. The starting point has an offset + // of 2*safety_distance because the + // original ray has also had an offset + auto q2 = m.query_ray_hit( + ps + (q.distance() + 2 * sd) * n, n); + hits[i] = q2; + } + } else + hits[i] = q; + }); + + auto mit = std::min_element(hits.begin(), hits.end()); + + return *mit; +} + +EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( + const Vec3d &s, const Vec3d &dir, double r, bool ins_check) +{ + static const size_t SAMPLES = 8; + + // helper vector calculations + Vec3d a(0, 1, 0), b; + const double& sd = m_cfg.safety_distance_mm; + + // INFO: for explanation of the method used here, see the previous + // method's comments. + + auto chk1 = [] (double val) { + return std::abs(std::abs(val) - 1) < 1e-20; + }; + + if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { + a = {dir(Z), dir(X), dir(Y)}; + b = {dir(Y), dir(Z), dir(X)}; + } + else { + a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); + b = a.cross(dir); + } + + // circle portions + std::array phis; + for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + + auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + ccr::enumerate( + phis.begin(), phis.end(), + [&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) { + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + // Let's have a safety coefficient for the radiuses. + double rcos = (sd + r) * cosphi; + double rsin = (sd + r) * sinphi; + + // Point on the circle on the pin sphere + Vec3d p (s(X) + rcos * a(X) + rsin * b(X), + s(Y) + rcos * a(Y) + rsin * b(Y), + s(Z) + rcos * a(Z) + rsin * b(Z)); + + auto hr = m.query_ray_hit(p + sd*dir, dir); + + if(ins_check && hr.is_inside()) { + if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); + else { + // re-cast the ray from the outside of the object + auto hr2 = + m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); + + hits[i] = hr2; + } + } else hits[i] = hr; + }); + + auto mit = std::min_element(hits.begin(), hits.end()); + + return *mit; +} + +bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, + const Pillar &nextpillar) +{ + // We need to get the starting point of the zig-zag pattern. We have to + // be aware that the two head junctions are at different heights. We + // may start from the lowest junction and call it a day but this + // strategy would leave unconnected a lot of pillar duos where the + // shorter pillar is too short to start a new bridge but the taller + // pillar could still be bridged with the shorter one. + bool was_connected = false; + + Vec3d supper = pillar.startpoint(); + Vec3d slower = nextpillar.startpoint(); + Vec3d eupper = pillar.endpoint(); + Vec3d elower = nextpillar.endpoint(); + + double zmin = m_builder.ground_level + m_cfg.base_height_mm; + eupper(Z) = std::max(eupper(Z), zmin); + elower(Z) = std::max(elower(Z), zmin); + + // The usable length of both pillars should be positive + if(slower(Z) - elower(Z) < 0) return false; + if(supper(Z) - eupper(Z) < 0) return false; + + double pillar_dist = distance(Vec2d{slower(X), slower(Y)}, + Vec2d{supper(X), supper(Y)}); + double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); + double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); + + if(pillar_dist < 2 * m_cfg.head_back_radius_mm || + pillar_dist > m_cfg.max_pillar_link_distance_mm) return false; + + if(supper(Z) < slower(Z)) supper.swap(slower); + if(eupper(Z) < elower(Z)) eupper.swap(elower); + + double startz = 0, endz = 0; + + startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); + endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); + + if(slower(Z) - eupper(Z) < std::abs(zstep)) { + // no space for even one cross + + // Get max available space + startz = std::min(supper(Z), slower(Z) - zstep); + endz = std::max(eupper(Z) + zstep, elower(Z)); + + // Align to center + double available_dist = (startz - endz); + double rounds = std::floor(available_dist / std::abs(zstep)); + startz -= 0.5 * (available_dist - rounds * std::abs(zstep)); + } + + auto pcm = m_cfg.pillar_connection_mode; + bool docrosses = + pcm == PillarConnectionMode::cross || + (pcm == PillarConnectionMode::dynamic && + pillar_dist > 2*m_cfg.base_radius_mm); + + // 'sj' means starting junction, 'ej' is the end junction of a bridge. + // They will be swapped in every iteration thus the zig-zag pattern. + // According to a config parameter, a second bridge may be added which + // results in a cross connection between the pillars. + Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; + + // TODO: This is a workaround to not have a faulty last bridge + while(ej(Z) >= eupper(Z) /*endz*/) { + if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance) + { + m_builder.add_crossbridge(sj, ej, pillar.r); + was_connected = true; + } + + // double bridging: (crosses) + if(docrosses) { + Vec3d sjback(ej(X), ej(Y), sj(Z)); + Vec3d ejback(sj(X), sj(Y), ej(Z)); + if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) && + bridge_mesh_intersect(sjback, dirv(sjback, ejback), + pillar.r) >= bridge_distance) { + // need to check collision for the cross stick + m_builder.add_crossbridge(sjback, ejback, pillar.r); + was_connected = true; + } + } + + sj.swap(ej); + ej(Z) = sj(Z) + zstep; + } + + return was_connected; +} + +bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, + long nearpillar_id) +{ + auto nearpillar = [this, nearpillar_id]() -> const Pillar& { + return m_builder.pillar(nearpillar_id); + }; + + if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar) + return false; + + Vec3d headjp = head.junction_point(); + Vec3d nearjp_u = nearpillar().startpoint(); + Vec3d nearjp_l = nearpillar().endpoint(); + + double r = head.r_back_mm; + double d2d = distance(to_2d(headjp), to_2d(nearjp_u)); + double d3d = distance(headjp, nearjp_u); + + double hdiff = nearjp_u(Z) - headjp(Z); + double slope = std::atan2(hdiff, d2d); + + Vec3d bridgestart = headjp; + Vec3d bridgeend = nearjp_u; + double max_len = m_cfg.max_bridge_length_mm; + double max_slope = m_cfg.bridge_slope; + double zdiff = 0.0; + + // check the default situation if feasible for a bridge + if(d3d > max_len || slope > -max_slope) { + // not feasible to connect the two head junctions. We have to search + // for a suitable touch point. + + double Zdown = headjp(Z) + d2d * std::tan(-max_slope); + Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; + double D = distance(headjp, touchjp); + zdiff = Zdown - nearjp_u(Z); + + if(zdiff > 0) { + Zdown -= zdiff; + bridgestart(Z) -= zdiff; + touchjp(Z) = Zdown; + + double t = bridge_mesh_intersect(headjp, {0,0,-1}, r); + + // We can't insert a pillar under the source head to connect + // with the nearby pillar's starting junction + if(t < zdiff) return false; + } + + if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len) + bridgeend(Z) = Zdown; + else + return false; + } + + // There will be a minimum distance from the ground where the + // bridge is allowed to connect. This is an empiric value. + double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm; + if(bridgeend(Z) < minz) return false; + + double t = bridge_mesh_intersect(bridgestart, + dirv(bridgestart, bridgeend), r); + + // Cannot insert the bridge. (further search might not worth the hassle) + if(t < distance(bridgestart, bridgeend)) return false; + + std::lock_guard lk(m_bridge_mutex); + + if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) { + // A partial pillar is needed under the starting head. + if(zdiff > 0) { + m_builder.add_pillar(head.id, bridgestart, r); + m_builder.add_junction(bridgestart, r); + m_builder.add_bridge(bridgestart, bridgeend, head.r_back_mm); + } else { + m_builder.add_bridge(head.id, bridgeend); + } + + m_builder.increment_bridges(nearpillar()); + } else return false; + + return true; +} + +bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &head) +{ + PointIndex spindex = m_pillar_index.guarded_clone(); + + long nearest_id = ID_UNSET; + + Vec3d querypoint = head.junction_point(); + + while(nearest_id < 0 && !spindex.empty()) { m_thr(); + // loop until a suitable head is not found + // if there is a pillar closer than the cluster center + // (this may happen as the clustering is not perfect) + // than we will bridge to this closer pillar + + Vec3d qp(querypoint(X), querypoint(Y), m_builder.ground_level); + auto qres = spindex.nearest(qp, 1); + if(qres.empty()) break; + + auto ne = qres.front(); + nearest_id = ne.second; + + if(nearest_id >= 0) { + if(size_t(nearest_id) < m_builder.pillarcount()) { + if(!connect_to_nearpillar(head, nearest_id)) { + nearest_id = ID_UNSET; // continue searching + spindex.remove(ne); // without the current pillar + } + } + } + } + + return nearest_id >= 0; +} + +void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, + const Vec3d &sourcedir, + double radius, + long head_id) +{ + // People were killed for this number (seriously) + static const double SQR2 = std::sqrt(2.0); + static const Vec3d DOWN = {0.0, 0.0, -1.0}; + + double gndlvl = m_builder.ground_level; + Vec3d endp = {jp(X), jp(Y), gndlvl}; + double sd = m_cfg.pillar_base_safety_distance_mm; + long pillar_id = ID_UNSET; + double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + double dist = 0; + bool can_add_base = true; + bool normal_mode = true; + + if (m_cfg.object_elevation_mm < EPSILON + && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { + // Get the distance from the mesh. This can be later optimized + // to get the distance in 2D plane because we are dealing with + // the ground level only. + + normal_mode = false; + double mind = min_dist - dist; + double azimuth = std::atan2(sourcedir(Y), sourcedir(X)); + double sinpolar = std::sin(PI - m_cfg.bridge_slope); + double cospolar = std::cos(PI - m_cfg.bridge_slope); + double cosazm = std::cos(azimuth); + double sinazm = std::sin(azimuth); + + auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) + .normalized(); + + using namespace libnest2d::opt; + StopCriteria scr; + scr.stop_score = min_dist; + SubplexOptimizer solver(scr); + + auto result = solver.optimize_max( + [this, dir, jp, gndlvl](double mv) { + Vec3d endpt = jp + SQR2 * mv * dir; + endpt(Z) = gndlvl; + return std::sqrt(m_mesh.squared_distance(endpt)); + }, + initvals(mind), bound(0.0, 2 * min_dist)); + + mind = std::get<0>(result.optimum); + endp = jp + SQR2 * mind * dir; + Vec3d pgnd = {endp(X), endp(Y), gndlvl}; + can_add_base = result.score > min_dist; + + double gnd_offs = m_mesh.ground_level_offset(); + auto abort_in_shame = + [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() + { + normal_mode = true; + can_add_base = false; // Nothing left to do, hope for the best + endp = {jp(X), jp(Y), gndlvl - gnd_offs }; + }; + + // We have to check if the bridge is feasible. + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) + abort_in_shame(); + else { + // If the new endpoint is below ground, do not make a pillar + if (endp(Z) < gndlvl) + endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off + else { + + auto hit = bridge_mesh_intersect(endp, DOWN, radius); + if (!std::isinf(hit.distance())) abort_in_shame(); + + pillar_id = m_builder.add_pillar(endp, pgnd, radius); + + if (can_add_base) + m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, + m_cfg.base_radius_mm); + } + + m_builder.add_bridge(jp, endp, radius); + m_builder.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_builder.add_pillar(head_id, jp, radius); + } + } + + if (normal_mode) { + pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : + m_builder.add_pillar(jp, endp, radius); + + if (can_add_base) + m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, + m_cfg.base_radius_mm); + } + + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index + m_pillar_index.guarded_insert(endp, unsigned(pillar_id)); +} + +void SupportTreeBuildsteps::filter() +{ + // Get the points that are too close to each other and keep only the + // first one + auto aliases = cluster(m_points, D_SP, 2); + + PtIndices filtered_indices; + filtered_indices.reserve(aliases.size()); + m_iheads.reserve(aliases.size()); + m_iheadless.reserve(aliases.size()); + for(auto& a : aliases) { + // Here we keep only the front point of the cluster. + filtered_indices.emplace_back(a.front()); + } + + // calculate the normals to the triangles for filtered points + auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm, + m_thr, filtered_indices); + + // Not all of the support points have to be a valid position for + // support creation. The angle may be inappropriate or there may + // not be enough space for the pinhead. Filtering is applied for + // these reasons. + + using libnest2d::opt::bound; + using libnest2d::opt::initvals; + using libnest2d::opt::GeneticOptimizer; + using libnest2d::opt::StopCriteria; + + ccr::SpinningMutex mutex; + auto addfn = [&mutex](PtIndices &container, unsigned val) { + std::lock_guard lk(mutex); + container.emplace_back(val); + }; + + auto filterfn = [this, &nmls, addfn](unsigned fidx, size_t i) { + m_thr(); + + auto n = nmls.row(Eigen::Index(i)); + + // for all normals we generate the spherical coordinates and + // saturate the polar angle to 45 degrees from the bottom then + // convert back to standard coordinates to get the new normal. + // Then we just create a quaternion from the two normals + // (Quaternion::FromTwoVectors) and apply the rotation to the + // arrow head. + + double z = n(2); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); + double azimuth = std::atan2(n(1), n(0)); + + // skip if the tilt is not sane + if(polar >= PI - m_cfg.normal_cutoff_angle) { + + // We saturate the polar angle to 3pi/4 + polar = std::max(polar, 3*PI / 4); + + // save the head (pinpoint) position + Vec3d hp = m_points.row(fidx); + + double w = m_cfg.head_width_mm + + m_cfg.head_back_radius_mm + + 2*m_cfg.head_front_radius_mm; + + double pin_r = double(m_support_pts[fidx].head_front_radius); + + // Reassemble the now corrected normal + auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + + // check available distance + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); + + if(t.distance() <= w) { + + // Let's try to optimize this angle, there might be a + // viable normal that doesn't collide with the model + // geometry and its very close to the default. + + StopCriteria stc; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; + stc.stop_score = w; // space greater than w is enough + GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior + + auto oresult = solver.optimize_max( + [this, pin_r, w, hp](double plr, double azm) + { + auto dir = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); + + double score = pinhead_mesh_intersect( + hp, dir, pin_r, m_cfg.head_back_radius_mm, w); + + return score; + }, + initvals(polar, azimuth), // start with what we have + bound(3 * PI / 4, + PI), // Must not exceed the tilt limit + bound(-PI, PI) // azimuth can be a full search + ); + + if(oresult.score > w) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + nn = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + t = oresult.score; + } + } + + // save the verified and corrected normal + m_support_nmls.row(fidx) = nn; + + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_builder.ground_level) { + addfn(m_iheadless, fidx); + } else { + // mark the point for needing a head. + addfn(m_iheads, fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. + addfn(m_iheadless, fidx); + } + } + }; + + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), filterfn); + + m_thr(); +} + +void SupportTreeBuildsteps::add_pinheads() +{ + for (unsigned i : m_iheads) { + m_thr(); + m_builder.add_head( + i, + m_cfg.head_back_radius_mm, + m_support_pts[i].head_front_radius, + m_cfg.head_width_mm, + m_cfg.head_penetration_mm, + m_support_nmls.row(i), // dir + m_support_pts[i].pos.cast() // displacement + ); + } +} + +void SupportTreeBuildsteps::classify() +{ + // We should first get the heads that reach the ground directly + PtIndices ground_head_indices; + ground_head_indices.reserve(m_iheads.size()); + m_iheads_onmodel.reserve(m_iheads.size()); + + // First we decide which heads reach the ground and can be full + // pillars and which shall be connected to the model surface (or + // search a suitable path around the surface that leads to the + // ground -- TODO) + for(unsigned i : m_iheads) { + m_thr(); + + auto& head = m_builder.head(i); + Vec3d n(0, 0, -1); + double r = head.r_back_mm; + Vec3d headjp = head.junction_point(); + + // collision check + auto hit = bridge_mesh_intersect(headjp, n, r); + + if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); + else if(m_cfg.ground_facing_only) head.invalidate(); + else m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); + } + + // We want to search for clusters of points that are far enough + // from each other in the XY plane to not cross their pillar bases + // These clusters of support points will join in one pillar, + // possibly in their centroid support point. + + auto pointfn = [this](unsigned i) { + return m_builder.head(i).junction_point(); + }; + + auto predicate = [this](const PointIndexEl &e1, + const PointIndexEl &e2) { + double d2d = distance(to_2d(e1.first), to_2d(e2.first)); + double d3d = distance(e1.first, e2.first); + return d2d < 2 * m_cfg.base_radius_mm + && d3d < m_cfg.max_bridge_length_mm; + }; + + m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + m_cfg.max_bridges_on_pillar); +} + +void SupportTreeBuildsteps::routing_to_ground() +{ + const double pradius = m_cfg.head_back_radius_mm; + + ClusterEl cl_centroids; + cl_centroids.reserve(m_pillar_clusters.size()); + + for (auto &cl : m_pillar_clusters) { + m_thr(); + + // place all the centroid head positions into the index. We + // will query for alternative pillar positions. If a sidehead + // cannot connect to the cluster centroid, we have to search + // for another head with a full pillar. Also when there are two + // elements in the cluster, the centroid is arbitrary and the + // sidehead is allowed to connect to a nearby pillar to + // increase structural stability. + + if (cl.empty()) continue; + + // get the current cluster centroid + auto & thr = m_thr; + const auto &points = m_points; + long lcid = cluster_centroid( + cl, [&points](size_t idx) { return points.row(long(idx)); }, + [thr](const Vec3d &p1, const Vec3d &p2) { + thr(); + return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y))); + }); + + assert(lcid >= 0); + unsigned hid = cl[size_t(lcid)]; // Head ID + + cl_centroids.emplace_back(hid); + + Head &h = m_builder.head(hid); + h.transform(); + + create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); + } + + // now we will go through the clusters ones again and connect the + // sidepoints with the cluster centroid (which is a ground pillar) + // or a nearby pillar if the centroid is unreachable. + size_t ci = 0; + for (auto cl : m_pillar_clusters) { + m_thr(); + + auto cidx = cl_centroids[ci++]; + + // TODO: don't consider the cluster centroid but calculate a + // central position where the pillar can be placed. this way + // the weight is distributed more effectively on the pillar. + + auto centerpillarID = m_builder.head_pillar(cidx).id; + + for (auto c : cl) { + m_thr(); + if (c == cidx) continue; + + auto &sidehead = m_builder.head(c); + sidehead.transform(); + + if (!connect_to_nearpillar(sidehead, centerpillarID) && + !search_pillar_and_connect(sidehead)) { + Vec3d pstart = sidehead.junction_point(); + // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + // Could not find a pillar, create one + create_ground_pillar(pstart, sidehead.dir, pradius, sidehead.id); + } + } + } +} + +void SupportTreeBuildsteps::routing_to_model() +{ + // We need to check if there is an easy way out to the bed surface. + // If it can be routed there with a bridge shorter than + // min_bridge_distance. + + // First we want to index the available pillars. The best is to connect + // these points to the available pillars + + auto routedown = [this](Head& head, const Vec3d& dir, double dist) + { + head.transform(); + Vec3d endp = head.junction_point() + dist * dir; + m_builder.add_bridge(head.id, endp); + m_builder.add_junction(endp, head.r_back_mm); + + this->create_ground_pillar(endp, dir, head.r_back_mm); + }; + + std::vector modelpillars; + ccr::SpinningMutex mutex; + + auto onmodelfn = + [this, routedown, &modelpillars, &mutex] + (const std::pair &el, size_t) + { + m_thr(); + unsigned idx = el.first; + EigenMesh3D::hit_result hit = el.second; + + auto& head = m_builder.head(idx); + Vec3d hjp = head.junction_point(); + + // ///////////////////////////////////////////////////////////////// + // Search nearby pillar + // ///////////////////////////////////////////////////////////////// + + if(search_pillar_and_connect(head)) { head.transform(); return; } + + // ///////////////////////////////////////////////////////////////// + // Try straight path + // ///////////////////////////////////////////////////////////////// + + // Cannot connect to nearby pillar. We will try to search for + // a route to the ground. + + double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm); + double d = 0, tdown = 0; + Vec3d dirdown(0.0, 0.0, -1.0); + + t = std::min(t, m_cfg.max_bridge_length_mm); + + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( + hjp + d*head.dir, + dirdown, head.r_back_mm))) { + d += head.r_back_mm; + } + + if(std::isinf(tdown)) { // we heave found a route to the ground + routedown(head, head.dir, d); return; + } + + // ///////////////////////////////////////////////////////////////// + // Optimize bridge direction + // ///////////////////////////////////////////////////////////////// + + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. + + // Get the spherical representation of the normal. its easier to + // work with. + double z = head.dir(Z); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); + double azimuth = std::atan2(head.dir(Y), head.dir(X)); + + using libnest2d::opt::bound; + using libnest2d::opt::initvals; + using libnest2d::opt::GeneticOptimizer; + using libnest2d::opt::StopCriteria; + + StopCriteria stc; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; + stc.stop_score = 1e6; + GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior + + double r_back = head.r_back_mm; + + auto oresult = solver.optimize_max( + [this, hjp, r_back](double plr, double azm) + { + Vec3d n = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); + return bridge_mesh_intersect(hjp, n, r_back); + }, + initvals(polar, azimuth), // let's start with what we have + bound(3*PI/4, PI), // Must not exceed the slope limit + bound(-PI, PI) // azimuth can be a full range search + ); + + d = 0; t = oresult.score; + + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + + t = std::min(t, m_cfg.max_bridge_length_mm); + + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( + hjp + d*bridgedir, + dirdown, + head.r_back_mm))) { + d += head.r_back_mm; + } + + if(std::isinf(tdown)) { // we heave found a route to the ground + routedown(head, bridgedir, d); return; + } + + // ///////////////////////////////////////////////////////////////// + // Route to model body + // ///////////////////////////////////////////////////////////////// + + double zangle = std::asin(hit.direction()(Z)); + zangle = std::max(zangle, PI/4); + double h = std::sin(zangle) * head.fullwidth(); + + // The width of the tail head that we would like to have... + h = std::min(hit.distance() - head.r_back_mm, h); + + if(h > 0) { + Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; + auto center_hit = m_mesh.query_ray_hit(hjp, dirdown); + + double hitdiff = center_hit.distance() - hit.distance(); + Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? + center_hit.position() : hit.position(); + + head.transform(); + + long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm); + Pillar &pill = m_builder.pillar(pillar_id); + + Vec3d taildir = endp - hitp; + double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; + double w = dist - 2 * head.r_pin_mm - head.r_back_mm; + + if (w < 0.) { + BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!"; + w = 0.; + } + + Head tailhead(head.r_back_mm, + head.r_pin_mm, + w, + m_cfg.head_penetration_mm, + taildir, + hitp); + + tailhead.transform(); + pill.base = tailhead.mesh; + + // Experimental: add the pillar to the index for cascading + std::lock_guard lk(mutex); + modelpillars.emplace_back(unsigned(pill.id)); + return; + } + + // We have failed to route this head. + BOOST_LOG_TRIVIAL(warning) + << "Failed to route model facing support point." + << " ID: " << idx; + head.invalidate(); + }; + + ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), onmodelfn); + + for(auto pillid : modelpillars) { + auto& pillar = m_builder.pillar(pillid); + m_pillar_index.insert(pillar.endpoint(), pillid); + } +} + +void SupportTreeBuildsteps::interconnect_pillars() +{ + // Now comes the algorithm that connects pillars with each other. + // Ideally every pillar should be connected with at least one of its + // neighbors if that neighbor is within max_pillar_link_distance + + // Pillars with height exceeding H1 will require at least one neighbor + // to connect with. Height exceeding H2 require two neighbors. + double H1 = m_cfg.max_solo_pillar_height_mm; + double H2 = m_cfg.max_dual_pillar_height_mm; + double d = m_cfg.max_pillar_link_distance_mm; + + //A connection between two pillars only counts if the height ratio is + // bigger than 50% + double min_height_ratio = 0.5; + + std::set pairs; + + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs + auto cascadefn = + [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) + { + Vec3d qp = el.first; // endpoint of the pillar + + const Pillar& pillar = m_builder.pillar(el.second); // actual pillar + + // Get the max number of neighbors a pillar should connect to + unsigned neighbors = m_cfg.pillar_cascade_neighbors; + + // connections are already enough for the pillar + if(pillar.links >= neighbors) return; + + // Query all remaining points within reach + auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ + return distance(e.first, qp) < d; + }); + + // sort the result by distance (have to check if this is needed) + std::sort(qres.begin(), qres.end(), + [qp](const PointIndexEl& e1, const PointIndexEl& e2){ + return distance(e1.first, qp) < distance(e2.first, qp); + }); + + for(auto& re : qres) { // process the queried neighbors + + if(re.second == el.second) continue; // Skip self + + auto a = el.second, b = re.second; + + // Get unique hash for the given pair (order doesn't matter) + auto hashval = pairhash(a, b); + + // Search for the pair amongst the remembered pairs + if(pairs.find(hashval) != pairs.end()) continue; + + const Pillar& neighborpillar = m_builder.pillar(re.second); + + // this neighbor is occupied, skip + if(neighborpillar.links >= neighbors) continue; + + if(interconnect(pillar, neighborpillar)) { + pairs.insert(hashval); + + // If the interconnection length between the two pillars is + // less than 50% of the longer pillar's height, don't count + if(pillar.height < H1 || + neighborpillar.height / pillar.height > min_height_ratio) + m_builder.increment_links(pillar); + + if(neighborpillar.height < H1 || + pillar.height / neighborpillar.height > min_height_ratio) + m_builder.increment_links(neighborpillar); + + } + + // connections are enough for one pillar + if(pillar.links >= neighbors) break; + } + }; + + // Run the cascade for the pillars in the index + m_pillar_index.foreach(cascadefn); + + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. + + size_t pillarcount = m_builder.pillarcount(); + + // Again, go through all pillars, this time in the whole support tree + // not just the index. + for(size_t pid = 0; pid < pillarcount; pid++) { + auto pillar = [this, pid]() { return m_builder.pillar(pid); }; + + // Decide how many additional pillars will be needed: + + unsigned needpillars = 0; + if (pillar().bridges > m_cfg.max_bridges_on_pillar) + needpillars = 3; + else if (pillar().links < 2 && pillar().height > H2) { + // Not enough neighbors to support this pillar + needpillars = 2; + } else if (pillar().links < 1 && pillar().height > H1) { + // No neighbors could be found and the pillar is too long. + needpillars = 1; + } + + needpillars = std::max(pillar().links, needpillars) - pillar().links; + if (needpillars == 0) continue; + + // Search for new pillar locations: + + bool found = false; + double alpha = 0; // goes to 2Pi + double r = 2 * m_cfg.base_radius_mm; + Vec3d pillarsp = pillar().startpoint(); + + // temp value for starting point detection + Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); + + // A vector of bool for placement feasbility + std::vector canplace(needpillars, false); + std::vector spts(needpillars); // vector of starting points + + double gnd = m_builder.ground_level; + double min_dist = m_cfg.pillar_base_safety_distance_mm + + m_cfg.base_radius_mm + EPSILON; + + while(!found && alpha < 2*PI) { + for (unsigned n = 0; + n < needpillars && (!n || canplace[n - 1]); + n++) + { + double a = alpha + n * PI / 3; + Vec3d s = sp; + s(X) += std::cos(a) * r; + s(Y) += std::sin(a) * r; + spts[n] = s; + + // Check the path vertically down + auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); + Vec3d gndsp{s(X), s(Y), gnd}; + + // If the path is clear, check for pillar base collisions + canplace[n] = std::isinf(hr.distance()) && + std::sqrt(m_mesh.squared_distance(gndsp)) > + min_dist; + } + + found = std::all_of(canplace.begin(), canplace.end(), + [](bool v) { return v; }); + + // 20 angles will be tried... + alpha += 0.1 * PI; + } + + std::vector newpills; + newpills.reserve(needpillars); + + if (found) + for (unsigned n = 0; n < needpillars; n++) { + Vec3d s = spts[n]; + Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); + p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + if (interconnect(pillar(), p)) { + Pillar &pp = m_builder.pillar(m_builder.add_pillar(p)); + m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); + + m_builder.add_junction(s, pillar().r); + double t = bridge_mesh_intersect(pillarsp, + dirv(pillarsp, s), + pillar().r); + if (distance(pillarsp, s) < t) + m_builder.add_bridge(pillarsp, s, pillar().r); + + if (pillar().endpoint()(Z) > m_builder.ground_level) + m_builder.add_junction(pillar().endpoint(), + pillar().r); + + newpills.emplace_back(pp.id); + m_builder.increment_links(pillar()); + m_builder.increment_links(pp); + } + } + + if(!newpills.empty()) { + for(auto it = newpills.begin(), nx = std::next(it); + nx != newpills.end(); ++it, ++nx) { + const Pillar& itpll = m_builder.pillar(*it); + const Pillar& nxpll = m_builder.pillar(*nx); + if(interconnect(itpll, nxpll)) { + m_builder.increment_links(itpll); + m_builder.increment_links(nxpll); + } + } + + m_pillar_index.foreach(cascadefn); + } + } +} + +void SupportTreeBuildsteps::routing_headless() +{ + // For now we will just generate smaller headless sticks with a sharp + // ending point that connects to the mesh surface. + + // We will sink the pins into the model surface for a distance of 1/3 of + // the pin radius + for(unsigned i : m_iheadless) { + m_thr(); + + const auto R = double(m_support_pts[i].head_front_radius); + const double HWIDTH_MM = m_cfg.head_penetration_mm; + + // Exact support position + Vec3d sph = m_support_pts[i].pos.cast(); + Vec3d n = m_support_nmls.row(i); // mesh outward normal + Vec3d sp = sph - n * HWIDTH_MM; // stick head start point + + Vec3d dir = {0, 0, -1}; + Vec3d sj = sp + R * n; // stick start point + + // This is only for checking + double idist = bridge_mesh_intersect(sph, dir, R, true); + double realdist = ray_mesh_intersect(sj, dir); + double dist = realdist; + + if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; + + if(std::isnan(idist) || idist < 2*R || std::isnan(dist) || dist < 2*R) { + BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" + << " support stick at: " + << sj.transpose(); + continue; + } + + bool use_endball = !std::isinf(realdist); + Vec3d ej = sj + (dist + HWIDTH_MM) * dir; + m_builder.add_compact_bridge(sp, ej, n, R, use_endball); + } +} + +} +} diff --git a/src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp new file mode 100644 index 0000000000..e953cc1361 --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp @@ -0,0 +1,289 @@ +#ifndef SLASUPPORTTREEALGORITHM_H +#define SLASUPPORTTREEALGORITHM_H + +#include + +#include "SLASupportTreeBuilder.hpp" + +namespace Slic3r { +namespace sla { + +// The minimum distance for two support points to remain valid. +const double /*constexpr*/ D_SP = 0.1; + +enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers + X, Y, Z +}; + +inline Vec2d to_vec2(const Vec3d& v3) { + return {v3(X), v3(Y)}; +} + +// This function returns the position of the centroid in the input 'clust' +// vector of point indices. +template +long cluster_centroid(const ClusterEl& clust, + const std::function &pointfn, + DistFn df) +{ + switch(clust.size()) { + case 0: /* empty cluster */ return ID_UNSET; + case 1: /* only one element */ return 0; + case 2: /* if two elements, there is no center */ return 0; + default: ; + } + + // The function works by calculating for each point the average distance + // from all the other points in the cluster. We create a selector bitmask of + // the same size as the cluster. The bitmask will have two true bits and + // false bits for the rest of items and we will loop through all the + // permutations of the bitmask (combinations of two points). Get the + // distance for the two points and add the distance to the averages. + // The point with the smallest average than wins. + + // The complexity should be O(n^2) but we will mostly apply this function + // for small clusters only (cca 3 elements) + + std::vector sel(clust.size(), false); // create full zero bitmask + std::fill(sel.end() - 2, sel.end(), true); // insert the two ones + std::vector avgs(clust.size(), 0.0); // store the average distances + + do { + std::array idx; + for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; + + double d = df(pointfn(clust[idx[0]]), + pointfn(clust[idx[1]])); + + // add the distance to the sums for both associated points + for(auto i : idx) avgs[i] += d; + + // now continue with the next permutation of the bitmask with two 1s + } while(std::next_permutation(sel.begin(), sel.end())); + + // Divide by point size in the cluster to get the average (may be redundant) + for(auto& a : avgs) a /= clust.size(); + + // get the lowest average distance and return the index + auto minit = std::min_element(avgs.begin(), avgs.end()); + return long(minit - avgs.begin()); +} + +inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { + return (endp - startp).normalized(); +} + +class PillarIndex { + PointIndex m_index; + using Mutex = ccr::BlockingMutex; + mutable Mutex m_mutex; + +public: + + template inline void guarded_insert(Args&&...args) + { + std::lock_guard lck(m_mutex); + m_index.insert(std::forward(args)...); + } + + template + inline std::vector guarded_query(Args&&...args) const + { + std::lock_guard lck(m_mutex); + return m_index.query(std::forward(args)...); + } + + template inline void insert(Args&&...args) + { + m_index.insert(std::forward(args)...); + } + + template + inline std::vector query(Args&&...args) const + { + return m_index.query(std::forward(args)...); + } + + template inline void foreach(Fn fn) { m_index.foreach(fn); } + template inline void guarded_foreach(Fn fn) + { + std::lock_guard lck(m_mutex); + m_index.foreach(fn); + } + + PointIndex guarded_clone() + { + std::lock_guard lck(m_mutex); + return m_index; + } +}; + +// Helper function for pillar interconnection where pairs of already connected +// pillars should be checked for not to be processed again. This can be done +// in constant time with a set of hash values uniquely representing a pair of +// integers. The order of numbers within the pair should not matter, it has +// the same unique hash. The hash value has to have twice as many bits as the +// arguments need. If the same integral type is used for args and return val, +// make sure the arguments use only the half of the type's bit depth. +template> +IntegerOnly pairhash(I a, I b) +{ + using std::ceil; using std::log2; using std::max; using std::min; + static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT); + static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT); + static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits; + + I g = min(a, b), l = max(a, b); + + // Assume the hash will fit into the output variable + assert((g ? (ceil(log2(g))) : 0) <= shift); + assert((l ? (ceil(log2(l))) : 0) <= shift); + + return (DoubleI(g) << shift) + l; +} + +class SupportTreeBuildsteps { + const SupportConfig& m_cfg; + const EigenMesh3D& m_mesh; + const std::vector& m_support_pts; + + using PtIndices = std::vector; + + PtIndices m_iheads; // support points with pinhead + PtIndices m_iheadless; // headless support points + + // supp. pts. connecting to model: point index and the ray hit data + std::vector> m_iheads_onmodel; + + // normals for support points from model faces. + PointSet m_support_nmls; + + // Clusters of points which can reach the ground directly and can be + // bridged to one central pillar + std::vector m_pillar_clusters; + + // This algorithm uses the SupportTreeBuilder class to fill gradually + // the support elements (heads, pillars, bridges, ...) + SupportTreeBuilder& m_builder; + + // support points in Eigen/IGL format + PointSet m_points; + + // throw if canceled: It will be called many times so a shorthand will + // come in handy. + ThrowOnCancel m_thr; + + // A spatial index to easily find strong pillars to connect to. + PillarIndex m_pillar_index; + + // When bridging heads to pillars... TODO: find a cleaner solution + ccr::BlockingMutex m_bridge_mutex; + + inline double ray_mesh_intersect(const Vec3d& s, + const Vec3d& dir) + { + return m_mesh.query_ray_hit(s, dir).distance(); + } + + // This function will test if a future pinhead would not collide with the + // model geometry. It does not take a 'Head' object because those are + // created after this test. Parameters: s: The touching point on the model + // surface. dir: This is the direction of the head from the pin to the back + // r_pin, r_back: the radiuses of the pin and the back sphere width: This + // is the full width from the pin center to the back center m: The object + // mesh. + // The return value is the hit result from the ray casting. If the starting + // point was inside the model, an "invalid" hit_result will be returned + // with a zero distance value instead of a NAN. This way the result can + // be used safely for comparison with other distances. + EigenMesh3D::hit_result pinhead_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r_pin, + double r_back, + double width); + + // Checking bridge (pillar and stick as well) intersection with the model. + // If the function is used for headless sticks, the ins_check parameter + // have to be true as the beginning of the stick might be inside the model + // geometry. + // The return value is the hit result from the ray casting. If the starting + // point was inside the model, an "invalid" hit_result will be returned + // with a zero distance value instead of a NAN. This way the result can + // be used safely for comparison with other distances. + EigenMesh3D::hit_result bridge_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r, + bool ins_check = false); + + // Helper function for interconnecting two pillars with zig-zag bridges. + bool interconnect(const Pillar& pillar, const Pillar& nextpillar); + + // For connecting a head to a nearby pillar. + bool connect_to_nearpillar(const Head& head, long nearpillar_id); + + bool search_pillar_and_connect(const Head& head); + + // This is a proxy function for pillar creation which will mind the gap + // between the pad and the model bottom in zero elevation mode. + void create_ground_pillar(const Vec3d &jp, + const Vec3d &sourcedir, + double radius, + long head_id = ID_UNSET); +public: + SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); + + // Now let's define the individual steps of the support generation algorithm + + // Filtering step: here we will discard inappropriate support points + // and decide the future of the appropriate ones. We will check if a + // pinhead is applicable and adjust its angle at each support point. We + // will also merge the support points that are just too close and can + // be considered as one. + void filter(); + + // Pinhead creation: based on the filtering results, the Head objects + // will be constructed (together with their triangle meshes). + void add_pinheads(); + + // Further classification of the support points with pinheads. If the + // ground is directly reachable through a vertical line parallel to the + // Z axis we consider a support point as pillar candidate. If touches + // the model geometry, it will be marked as non-ground facing and + // further steps will process it. Also, the pillars will be grouped + // into clusters that can be interconnected with bridges. Elements of + // these groups may or may not be interconnected. Here we only run the + // clustering algorithm. + void classify(); + + // Step: Routing the ground connected pinheads, and interconnecting + // them with additional (angled) bridges. Not all of these pinheads + // will be a full pillar (ground connected). Some will connect to a + // nearby pillar using a bridge. The max number of such side-heads for + // a central pillar is limited to avoid bad weight distribution. + void routing_to_ground(); + + // Step: routing the pinheads that would connect to the model surface + // along the Z axis downwards. For now these will actually be connected with + // the model surface with a flipped pinhead. In the future here we could use + // some smart algorithms to search for a safe path to the ground or to a + // nearby pillar that can hold the supported weight. + void routing_to_model(); + + void interconnect_pillars(); + + // Step: process the support points where there is not enough space for a + // full pinhead. In this case we will use a rounded sphere as a touching + // point and use a thinner bridge (let's call it a stick). + void routing_headless (); + + inline void merge_result() { m_builder.merged_mesh(); } + + static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm); +}; + +} +} + +#endif // SLASUPPORTTREEALGORITHM_H diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 95d4512710..05f8b19842 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -77,7 +77,7 @@ bool PointIndex::remove(const PointIndexEl& el) } std::vector -PointIndex::query(std::function fn) +PointIndex::query(std::function fn) const { namespace bgi = boost::geometry::index; @@ -86,7 +86,7 @@ PointIndex::query(std::function fn) return ret; } -std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const { namespace bgi = boost::geometry::index; std::vector ret; ret.reserve(k); @@ -104,6 +104,11 @@ void PointIndex::foreach(std::function fn) for(auto& el : m_impl->m_store) fn(el); } +void PointIndex::foreach(std::function fn) const +{ + for(const auto &el : m_impl->m_store) fn(el); +} + /* ************************************************************************** * BoxIndex implementation * ************************************************************************** */ @@ -274,6 +279,8 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { * Misc functions * ****************************************************************************/ +namespace { + bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, double eps = 0.05) { @@ -289,11 +296,13 @@ template double distance(const Vec& pp1, const Vec& pp2) { return std::sqrt(p.transpose() * p); } +} + PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, std::function thr, // throw on cancel - const std::vector& pt_indices = {}) + const std::vector& pt_indices) { if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) return {}; @@ -419,9 +428,17 @@ PointSet normals(const PointSet& points, 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( @@ -433,25 +450,22 @@ ClusteredPoints cluster(Index3D &sindex, // each other std::function group = [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) - { + { for(auto& p : pts) { std::vector tmp = qfn(sindex, p); - auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ - return e1.second < e2.second; - }; - - std::sort(tmp.begin(), tmp.end(), cmp); + + 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); + 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); + std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); if(!newpts.empty() && (!max_points || cluster.size() < max_points)) group(newpts, cluster); @@ -479,7 +493,6 @@ ClusteredPoints cluster(Index3D &sindex, return result; } -namespace { std::vector distance_queryfn(const Index3D& sindex, const PointIndexEl& p, double dist, @@ -496,7 +509,8 @@ std::vector distance_queryfn(const Index3D& sindex, return tmp; } -} + +} // namespace // Clustering a set of points by the given criteria ClusteredPoints cluster( @@ -558,5 +572,5 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) }); } -} -} +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 6048996524..d9f8b7b6db 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,6 +1,6 @@ #include "SLAPrint.hpp" #include "SLA/SLASupportTree.hpp" -#include "SLA/SLABasePool.hpp" +#include "SLA/SLAPad.hpp" #include "SLA/SLAAutoSupports.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" @@ -32,17 +32,19 @@ namespace Slic3r { -using SupportTreePtr = std::unique_ptr; - -class SLAPrintObject::SupportData +class SLAPrintObject::SupportData : public sla::SupportableMesh { public: - sla::EigenMesh3D emesh; // index-triangle representation - std::vector support_points; // all the support points (manual/auto) - SupportTreePtr support_tree_ptr; // the supports + sla::SupportTree::UPtr support_tree_ptr; // the supports std::vector support_slices; // sliced supports - - inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} + + 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; + } }; namespace { @@ -53,7 +55,7 @@ const std::array OBJ_STEP_LEVELS = 30, // slaposObjectSlice, 20, // slaposSupportPoints, 10, // slaposSupportTree, - 10, // slaposBasePool, + 10, // slaposPad, 30, // slaposSliceSupports, }; @@ -64,7 +66,7 @@ std::string OBJ_STEP_LABELS(size_t idx) case slaposObjectSlice: return L("Slicing model"); case slaposSupportPoints: return L("Generating support points"); case slaposSupportTree: return L("Generating support tree"); - case slaposBasePool: return L("Generating pad"); + case slaposPad: return L("Generating pad"); case slaposSliceSupports: return L("Slicing supports"); default:; } @@ -583,7 +585,8 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) { // Compile the argument for support creation from the static print config. sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { sla::SupportConfig scfg; - + + scfg.enabled = c.supports_enable.getBool(); scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); scfg.head_penetration_mm = c.support_head_penetration.getFloat(); @@ -612,12 +615,13 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } -sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { - sla::PoolConfig::EmbedObject ret; +sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { + sla::PadConfig::EmbedObject ret; ret.enabled = is_zero_elevation(c); if(ret.enabled) { + ret.everywhere = c.pad_around_object_everywhere.getBool(); ret.object_gap_mm = c.pad_object_gap.getFloat(); ret.stick_width_mm = c.pad_object_connector_width.getFloat(); ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); @@ -628,17 +632,15 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { return ret; } -sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { - sla::PoolConfig pcfg; +sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) { + sla::PadConfig pcfg; - pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); + pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat(); pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; - // We do not support radius for now - pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); - - pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); - pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); + pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat(); + pcfg.wall_height_mm = c.pad_wall_height.getFloat(); + pcfg.brim_size_mm = c.pad_brim_size.getFloat(); // set builtin pad implicitly ON pcfg.embed_object = builtin_pad_cfg(c); @@ -646,6 +648,13 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { return pcfg; } +bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg) +{ + // An empty pad can only be created if embed_object mode is enabled + // and the pad is not forced everywhere + return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere); +} + } std::string SLAPrint::validate() const @@ -663,17 +672,12 @@ std::string SLAPrint::validate() const sla::SupportConfig cfg = make_support_cfg(po->config()); - double pinhead_width = - 2 * cfg.head_front_radius_mm + - cfg.head_width_mm + - 2 * cfg.head_back_radius_mm - - cfg.head_penetration_mm; - double elv = cfg.object_elevation_mm; - - sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); - if(supports_en && !builtinpad.enabled && elv < pinhead_width ) + sla::PadConfig padcfg = make_pad_cfg(po->config()); + sla::PadConfig::EmbedObject &builtinpad = padcfg.embed_object; + + if(supports_en && !builtinpad.enabled && elv < cfg.head_fullwidth()) return L( "Elevation is too low for object. Use the \"Pad around " "object\" feature to print the object without elevation."); @@ -686,6 +690,9 @@ std::string SLAPrint::validate() const "distance' has to be greater than the 'Pad object gap' " "parameter to avoid this."); } + + std::string pval = padcfg.validate(); + if (!pval.empty()) return pval; } double expt_max = m_printer_config.max_exposure_time.getFloat(); @@ -876,8 +883,7 @@ void SLAPrint::process() // Construction of this object does the calculation. this->throw_if_canceled(); - SLAAutoSupports auto_supports(po.transformed_mesh(), - po.m_supportdata->emesh, + SLAAutoSupports auto_supports(po.m_supportdata->emesh, po.get_model_slices(), heights, config, @@ -887,10 +893,10 @@ void SLAPrint::process() // Now let's extract the result. const std::vector& points = auto_supports.output(); this->throw_if_canceled(); - po.m_supportdata->support_points = points; + po.m_supportdata->pts = points; BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " - << po.m_supportdata->support_points.size(); + << po.m_supportdata->pts.size(); // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass // the update status to GLGizmoSlaSupports @@ -902,29 +908,19 @@ void SLAPrint::process() 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->support_points = po.transformed_support_points(); + 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 gnd = po.m_supportdata->emesh.ground_level(); - auto & pts = po.m_supportdata->support_points; double tolerance = po.config().pad_enable.getBool() ? po.m_config.pad_wall_thickness.getFloat() : po.m_config.support_base_height.getFloat(); - // get iterator to the reorganized vector end - auto endit = std::remove_if( - pts.begin(), - pts.end(), - [tolerance, gnd](const sla::SupportPoint &sp) { - double diff = std::abs(gnd - double(sp.pos(Z))); - return diff <= tolerance; - }); - - // erase all elements after the new end - pts.erase(endit, pts.end()); + remove_bottom_points(po.m_supportdata->pts, + po.m_supportdata->emesh.ground_level(), + tolerance); } }; @@ -933,45 +929,31 @@ void SLAPrint::process() { if(!po.m_supportdata) return; - sla::PoolConfig pcfg = make_pool_config(po.m_config); + sla::PadConfig pcfg = make_pad_cfg(po.m_config); if (pcfg.embed_object) - po.m_supportdata->emesh.ground_level_offset( - pcfg.min_wall_thickness_mm); - - if(!po.m_config.supports_enable.getBool()) { - - // Generate empty support tree. It can still host a pad - po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(po.m_supportdata->emesh.ground_level())); - - return; - } - - sla::SupportConfig scfg = make_support_cfg(po.m_config); - sla::Controller ctl; - + po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); + + po.m_supportdata->cfg = make_support_cfg(po.m_config); + // scaling for the sub operations double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double init = m_report_status.status(); + JobController ctl; - ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) - { + ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { double current = init + st * d; - if(std::round(m_report_status.status()) < std::round(current)) + if (std::round(m_report_status.status()) < std::round(current)) m_report_status(*this, current, OBJ_STEP_LABELS(slaposSupportTree), - SlicingStatus::DEFAULT, - logmsg); - + SlicingStatus::DEFAULT, logmsg); }; - - ctl.stopcondition = [this](){ return canceled(); }; + ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - - po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(po.m_supportdata->support_points, - po.m_supportdata->emesh, scfg, ctl)); + + po.m_supportdata->create_support_tree(ctl); + + if (!po.m_config.supports_enable.getBool()) return; throw_if_canceled(); @@ -980,10 +962,9 @@ void SLAPrint::process() // This is to prevent "Done." being displayed during merged_mesh() m_report_status(*this, -1, L("Visualizing supports")); - po.m_supportdata->support_tree_ptr->merged_mesh(); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " - << po.m_supportdata->support_points.size(); + << po.m_supportdata->pts.size(); // Check the mesh for later troubleshooting. if(po.support_mesh().empty()) @@ -993,7 +974,7 @@ void SLAPrint::process() }; // This step generates the sla base pad - auto base_pool = [this](SLAPrintObject& po) { + auto generate_pad = [this](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) @@ -1001,10 +982,10 @@ void SLAPrint::process() if(po.m_config.pad_enable.getBool()) { // Get the distilled pad configuration from the config - sla::PoolConfig pcfg = make_pool_config(po.m_config); + sla::PadConfig pcfg = make_pad_cfg(po.m_config); ExPolygons bp; // This will store the base plate of the pad. - double pad_h = sla::get_pad_fullheight(pcfg); + double pad_h = pcfg.full_height(); const TriangleMesh &trmesh = po.transformed_mesh(); // This call can get pretty time consuming @@ -1015,15 +996,19 @@ void SLAPrint::process() // 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::base_plate(trmesh, - bp, - float(pad_h), - float(po.m_config.layer_height.getFloat()), - thrfn); + sla::pad_blueprint(trmesh, bp, float(pad_h), + float(po.m_config.layer_height.getFloat()), + thrfn); } - pcfg.throw_on_cancel = thrfn; po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); + auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(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(); } @@ -1191,9 +1176,8 @@ void SLAPrint::process() { ClipperPolygon poly; - // We need to reverse if flpXY OR is_lefthanded is true but - // not if both are true which is a logical inequality (XOR) - bool needreverse = /*flpXY !=*/ is_lefthanded; + // 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); @@ -1388,7 +1372,12 @@ void SLAPrint::process() m_print_statistics.fast_layers_count = fast_layers; m_print_statistics.slow_layers_count = slow_layers; +#if ENABLE_THUMBNAIL_GENERATOR + // second argument set to -3 to differentiate it from the same call made into slice_supports() + m_report_status(*this, -3, "", SlicingStatus::RELOAD_SLA_PREVIEW); +#else m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); +#endif // ENABLE_THUMBNAIL_GENERATOR }; // Rasterizing the model objects, and their supports @@ -1396,7 +1385,7 @@ void SLAPrint::process() if(canceled()) return; // Set up the printer, allocate space for all the layers - sla::SLARasterWriter &printer = init_printer(); + sla::RasterWriter &printer = init_printer(); auto lvlcnt = unsigned(m_printer_input.size()); printer.layers(lvlcnt); @@ -1416,11 +1405,9 @@ void SLAPrint::process() SpinMutex slck; - auto orientation = get_printer_orientation(); - // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &printer, increment, &dstatus, &pst, orientation] + [this, &slck, &printer, increment, &dstatus, &pst] (unsigned level_id) { if(canceled()) return; @@ -1431,7 +1418,7 @@ void SLAPrint::process() printer.begin_layer(level_id); for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) - printer.draw_polygon(poly, level_id, orientation); + printer.draw_polygon(poly, level_id); // Finish the layer for later saving it. printer.finish_layer(level_id); @@ -1459,7 +1446,7 @@ void SLAPrint::process() tbb::parallel_for(0, lvlcnt, lvlfn); // Set statistics values to the printer - sla::SLARasterWriter::PrintStatistics stats; + sla::RasterWriter::PrintStatistics stats; stats.used_material = (m_print_statistics.objects_used_material + m_print_statistics.support_used_material) / 1000; @@ -1478,12 +1465,12 @@ void SLAPrint::process() slaposFn pobj_program[] = { - slice_model, support_points, support_tree, base_pool, slice_supports + slice_model, support_points, support_tree, generate_pad, slice_supports }; // We want to first process all objects... std::vector level1_obj_steps = { - slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool + slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad }; // and then slice all supports to allow preview to be displayed ASAP @@ -1658,12 +1645,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector mirror; - double gamma; double w = m_printer_config.display_width.getFloat(); double h = m_printer_config.display_height.getFloat(); @@ -1673,20 +1659,18 @@ sla::SLARasterWriter & SLAPrint::init_printer() mirror[X] = m_printer_config.display_mirror_x.getBool(); mirror[Y] = m_printer_config.display_mirror_y.getBool(); - if (get_printer_orientation() == sla::SLARasterWriter::roPortrait) { + auto orientation = get_printer_orientation(); + if (orientation == sla::Raster::roPortrait) { std::swap(w, h); std::swap(pw, ph); - - // XY flipping implicitly does an X mirror - mirror[X] = !mirror[X]; } res = sla::Raster::Resolution{pw, ph}; pxdim = sla::Raster::PixelDim{w / pw, h / ph}; - - gamma = m_printer_config.gamma_correction.getFloat(); - - m_printer.reset(new sla::SLARasterWriter(res, pxdim, mirror, gamma)); + sla::Raster::Trafo tr{orientation, mirror}; + tr.gamma = m_printer_config.gamma_correction.getFloat(); + + m_printer.reset(new sla::RasterWriter(res, pxdim, tr)); m_printer->set_config(m_full_print_config); return *m_printer; } @@ -1734,6 +1718,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vectorinvalidate_all_steps(); } else if (step == slaposSupportPoints) { - invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports }); + invalidated |= this->invalidate_steps({ slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSupportTree) { - invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports }); + invalidated |= this->invalidate_steps({ slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); - } else if (step == slaposBasePool) { + } else if (step == slaposPad) { invalidated |= this->invalidate_steps({slaposSliceSupports}); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSliceSupports) { @@ -1817,8 +1803,8 @@ double SLAPrintObject::get_elevation() const { // its walls but currently it is half of its thickness. Whatever it // will be in the future, we provide the config to the get_pad_elevation // method and we will have the correct value - sla::PoolConfig pcfg = make_pool_config(m_config); - if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); + sla::PadConfig pcfg = make_pad_cfg(m_config); + if(!pcfg.embed_object) ret += pcfg.required_elevation(); } return ret; @@ -1829,7 +1815,7 @@ double SLAPrintObject::get_current_elevation() const if (is_zero_elevation(m_config)) return 0.; bool has_supports = is_step_done(slaposSupportTree); - bool has_pad = is_step_done(slaposBasePool); + bool has_pad = is_step_done(slaposPad); if(!has_supports && !has_pad) return 0; @@ -1870,7 +1856,7 @@ const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); const std::vector& SLAPrintObject::get_support_points() const { - return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; + return m_supportdata? m_supportdata->pts : EMPTY_SUPPORT_POINTS; } const std::vector &SLAPrintObject::get_support_slices() const @@ -1900,7 +1886,7 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const switch (step) { case slaposSupportTree: return ! this->support_mesh().empty(); - case slaposBasePool: + case slaposPad: return ! this->pad_mesh().empty(); default: return false; @@ -1912,7 +1898,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const switch (step) { case slaposSupportTree: return this->support_mesh(); - case slaposBasePool: + case slaposPad: return this->pad_mesh(); default: return TriangleMesh(); @@ -1921,18 +1907,20 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const const TriangleMesh& SLAPrintObject::support_mesh() const { - if(m_config.supports_enable.getBool() && m_supportdata && - m_supportdata->support_tree_ptr) { - return m_supportdata->support_tree_ptr->merged_mesh(); - } - + sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr; + + if(m_config.supports_enable.getBool() && m_supportdata && stree) + return stree->retrieve_mesh(sla::MeshType::Support); + return EMPTY_MESH; } const TriangleMesh& SLAPrintObject::pad_mesh() const { - if(m_config.pad_enable.getBool() && m_supportdata && m_supportdata->support_tree_ptr) - return m_supportdata->support_tree_ptr->get_pad(); + sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr; + + if(m_config.pad_enable.getBool() && m_supportdata && stree) + return stree->retrieve_mesh(sla::MeshType::Pad); return EMPTY_MESH; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a2bc1325a6..8f386d407f 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -7,6 +7,7 @@ #include "SLA/SLARasterWriter.hpp" #include "Point.hpp" #include "MTUtils.hpp" +#include "Zipper.hpp" #include namespace Slic3r { @@ -21,7 +22,7 @@ enum SLAPrintObjectStep : unsigned int { slaposObjectSlice, slaposSupportPoints, slaposSupportTree, - slaposBasePool, + slaposPad, slaposSliceSupports, slaposCount }; @@ -54,7 +55,7 @@ public: bool is_left_handed() const { return m_left_handed; } struct Instance { - Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + Instance(ObjectID inst_id, const Point &shft, float rot) : instance_id(inst_id), shift(shft), rotation(rot) {} bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } // ID of the corresponding ModelInstance. ObjectID instance_id; @@ -364,6 +365,12 @@ public: if(m_printer) m_printer->save(fpath, projectname); } + inline void export_raster(Zipper &zipper, + const std::string& projectname = "") + { + if(m_printer) m_printer->save(zipper, projectname); + } + const PrintObjects& objects() const { return m_objects; } const SLAPrintConfig& print_config() const { return m_print_config; } @@ -440,7 +447,7 @@ private: std::vector m_printer_input; // The printer itself - std::unique_ptr m_printer; + std::unique_ptr m_printer; // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; @@ -459,14 +466,13 @@ private: double status() const { return m_st; } } m_report_status; - sla::SLARasterWriter &init_printer(); + sla::RasterWriter &init_printer(); - inline sla::SLARasterWriter::Orientation get_printer_orientation() const + inline sla::Raster::Orientation get_printer_orientation() const { auto ro = m_printer_config.display_orientation.getInt(); - return ro == sla::SLARasterWriter::roPortrait ? - sla::SLARasterWriter::roPortrait : - sla::SLARasterWriter::roLandscape; + return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait : + sla::Raster::roLandscape; } friend SLAPrintObject; diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 03f55802ef..6e4b973eac 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -368,6 +368,10 @@ void SVG::export_expolygons(const char *path, const std::vector 0) + for (const ExPolygon &expoly : exp_with_attr.first) + svg.draw((Points)expoly, exp_with_attr.second.color_points, exp_with_attr.second.radius_points); svg.Close(); } diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 3a56021963..c1b387554c 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -105,19 +105,25 @@ public: const std::string &color_contour, const std::string &color_holes, const coord_t outline_width = scale_(0.05), - const float fill_opacity = 0.5f) : + const float fill_opacity = 0.5f, + const std::string &color_points = "black", + const coord_t radius_points = 0) : color_fill (color_fill), color_contour (color_contour), color_holes (color_holes), outline_width (outline_width), - fill_opacity (fill_opacity) + fill_opacity (fill_opacity), + color_points (color_points), + radius_points (radius_points) {} std::string color_fill; std::string color_contour; std::string color_holes; + std::string color_points; coord_t outline_width; float fill_opacity; + coord_t radius_points; }; static void export_expolygons(const char *path, const std::vector> &expolygons_with_attributes); diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp new file mode 100644 index 0000000000..a0fb050057 --- /dev/null +++ b/src/libslic3r/ShortestPath.cpp @@ -0,0 +1,514 @@ +#if 0 + #pragma optimize("", off) + #undef NDEBUG + #undef assert +#endif + +#include "clipper.hpp" +#include "ShortestPath.hpp" +#include "KDTreeIndirect.hpp" +#include "MutablePriorityQueue.hpp" +#include "Print.hpp" + +#include +#include + +namespace Slic3r { + +// Naive implementation of the Traveling Salesman Problem, it works by always taking the next closest neighbor. +// This implementation will always produce valid result even if some segments cannot reverse. +template +std::vector> chain_segments_closest_point(std::vector &end_points, KDTreeType &kdtree, CouldReverseFunc &could_reverse_func, EndPointType &first_point) +{ + assert((end_points.size() & 1) == 0); + size_t num_segments = end_points.size() / 2; + assert(num_segments >= 2); + for (EndPointType &ep : end_points) + ep.chain_id = 0; + std::vector> out; + out.reserve(num_segments); + size_t first_point_idx = &first_point - end_points.data(); + out.emplace_back(first_point_idx / 2, (first_point_idx & 1) != 0); + first_point.chain_id = 1; + size_t this_idx = first_point_idx ^ 1; + for (int iter = (int)num_segments - 2; iter >= 0; -- iter) { + EndPointType &this_point = end_points[this_idx]; + this_point.chain_id = 1; + // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda). + // Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it. + size_t next_idx = find_closest_point(kdtree, this_point.pos, + [this_idx, &end_points, &could_reverse_func](size_t idx) { + return (idx ^ this_idx) > 1 && end_points[idx].chain_id == 0 && ((idx ^ 1) == 0 || could_reverse_func(idx >> 1)); + }); + assert(next_idx < end_points.size()); + EndPointType &end_point = end_points[next_idx]; + end_point.chain_id = 1; + this_idx = next_idx ^ 1; + } +#ifndef NDEBUG + assert(end_points[this_idx].chain_id == 0); + for (EndPointType &ep : end_points) + assert(&ep == &end_points[this_idx] || ep.chain_id == 1); +#endif /* NDEBUG */ + return out; +} + +// Chain perimeters (always closed) and thin fills (closed or open) using a greedy algorithm. +// Solving a Traveling Salesman Problem (TSP) with the modification, that the sites are not always points, but points and segments. +// Solving using a greedy algorithm, where a shortest edge is added to the solution if it does not produce a bifurcation or a cycle. +// Return index and "reversed" flag. +// https://en.wikipedia.org/wiki/Multi-fragment_algorithm +// The algorithm builds a tour for the traveling salesman one edge at a time and thus maintains multiple tour fragments, each of which +// is a simple path in the complete graph of cities. At each stage, the algorithm selects the edge of minimal cost that either creates +// a new fragment, extends one of the existing paths or creates a cycle of length equal to the number of cities. +template +std::vector> chain_segments_greedy_constrained_reversals_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near) +{ + std::vector> out; + + if (num_segments == 0) { + // Nothing to do. + } + else if (num_segments == 1) + { + // Just sort the end points so that the first point visited is closest to start_near. + out.emplace_back(0, start_near != nullptr && + (end_point_func(0, true) - *start_near).template cast().squaredNorm() < (end_point_func(0, false) - *start_near).template cast().squaredNorm()); + } + else + { + // End points of segments for the KD tree closest point search. + // A single end point is inserted into the search structure for loops, two end points are entered for open paths. + struct EndPoint { + EndPoint(const Vec2d &pos) : pos(pos) {} + Vec2d pos; + // Identifier of the chain, to which this end point belongs. Zero means unassigned. + size_t chain_id = 0; + // Link to the closest currently valid end point. + EndPoint *edge_out = nullptr; + // Distance to the next end point following the link. + // Zero value -> start of the final path. + double distance_out = std::numeric_limits::max(); + size_t heap_idx = std::numeric_limits::max(); + }; + std::vector end_points; + end_points.reserve(num_segments * 2); + for (size_t i = 0; i < num_segments; ++ i) { + end_points.emplace_back(end_point_func(i, true ).template cast()); + end_points.emplace_back(end_point_func(i, false).template cast()); + } + + // Construct the closest point KD tree over end points of segments. + auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; }; + KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size()); + + // Helper to detect loops in already connected paths. + // Unique chain IDs are assigned to paths. If paths are connected, end points will not have their chain IDs updated, but the chain IDs + // will remember an "equivalent" chain ID, which is the lowest ID of all the IDs in the path, and the lowest ID is equivalent to itself. + class EquivalentChains { + public: + // Zero'th chain ID is invalid. + EquivalentChains(size_t reserve) { m_equivalent_with.reserve(reserve); m_equivalent_with.emplace_back(0); } + // Generate next equivalence class. + size_t next() { + m_equivalent_with.emplace_back(++ m_last_chain_id); + return m_last_chain_id; + } + // Get equivalence class for chain ID. + size_t operator()(size_t chain_id) { + if (chain_id != 0) { + for (size_t last = chain_id;;) { + size_t lower = m_equivalent_with[last]; + if (lower == last) { + m_equivalent_with[chain_id] = lower; + chain_id = lower; + break; + } + last = lower; + } + } + return chain_id; + } + size_t merge(size_t chain_id1, size_t chain_id2) { + size_t chain_id = std::min((*this)(chain_id1), (*this)(chain_id2)); + m_equivalent_with[chain_id1] = chain_id; + m_equivalent_with[chain_id2] = chain_id; + return chain_id; + } + +#ifndef NDEBUG + bool validate() + { + assert(m_last_chain_id >= 0); + assert(m_last_chain_id + 1 == m_equivalent_with.size()); + for (size_t i = 0; i < m_equivalent_with.size(); ++ i) { + for (size_t last = i;;) { + size_t lower = m_equivalent_with[last]; + assert(lower <= last); + if (lower == last) + break; + last = lower; + } + } + return true; + } +#endif /* NDEBUG */ + + private: + // Unique chain ID assigned to chains of end points of segments. + size_t m_last_chain_id = 0; + std::vector m_equivalent_with; + } equivalent_chain(num_segments); + + // Find the first end point closest to start_near. + EndPoint *first_point = nullptr; + size_t first_point_idx = std::numeric_limits::max(); + if (start_near != nullptr) { + size_t idx = find_closest_point(kdtree, start_near->template cast()); + assert(idx < end_points.size()); + first_point = &end_points[idx]; + first_point->distance_out = 0.; + first_point->chain_id = equivalent_chain.next(); + first_point_idx = idx; + } + EndPoint *initial_point = first_point; + EndPoint *last_point = nullptr; + + // Assign the closest point and distance to the end points. + for (EndPoint &end_point : end_points) { + assert(end_point.edge_out == nullptr); + if (&end_point != first_point) { + size_t this_idx = &end_point - &end_points.front(); + // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda). + // Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it. + size_t next_idx = find_closest_point(kdtree, end_point.pos, + [this_idx, first_point_idx](size_t idx){ return idx != first_point_idx && (idx ^ this_idx) > 1; }); + assert(next_idx < end_points.size()); + EndPoint &end_point2 = end_points[next_idx]; + end_point.edge_out = &end_point2; + end_point.distance_out = (end_point2.pos - end_point.pos).squaredNorm(); + } + } + + // Initialize a heap of end points sorted by the lowest distance to the next valid point of a path. + auto queue = make_mutable_priority_queue( + [](EndPoint *ep, size_t idx){ ep->heap_idx = idx; }, + [](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; }); + queue.reserve(end_points.size() * 2 - 1); + for (EndPoint &ep : end_points) + if (first_point != &ep) + queue.push(&ep); + +#ifndef NDEBUG + auto validate_graph_and_queue = [&equivalent_chain, &end_points, &queue, first_point]() -> bool { + assert(equivalent_chain.validate()); + for (EndPoint &ep : end_points) { + if (ep.heap_idx < queue.size()) { + // End point is on the heap. + assert(*(queue.cbegin() + ep.heap_idx) == &ep); + assert(ep.chain_id == 0); + } else { + // End point is NOT on the heap, therefore it is part of the output path. + assert(ep.heap_idx == std::numeric_limits::max()); + assert(ep.chain_id != 0); + if (&ep == first_point) { + assert(ep.edge_out == nullptr); + } else { + assert(ep.edge_out != nullptr); + // Detect loops. + for (EndPoint *pt = &ep; pt != nullptr;) { + // Out of queue. It is a final point. + assert(pt->heap_idx == std::numeric_limits::max()); + EndPoint *pt_other = &end_points[(pt - &end_points.front()) ^ 1]; + if (pt_other->heap_idx < queue.size()) + // The other side of this segment is undecided yet. + break; + pt = pt_other->edge_out; + } + } + } + } + for (EndPoint *ep : queue) + // Points in the queue are not connected yet. + assert(ep->chain_id == 0); + return true; + }; +#endif /* NDEBUG */ + + // Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops. + assert(num_segments >= 2); + for (int iter = int(num_segments) - 2;; -- iter) { + assert(validate_graph_and_queue()); + // Take the first end point, for which the link points to the currently closest valid neighbor. + EndPoint &end_point1 = *queue.top(); + assert(end_point1.edge_out != nullptr); + // No point on the queue may be connected yet. + assert(end_point1.chain_id == 0); + // Take the closest end point to the first end point, + EndPoint &end_point2 = *end_point1.edge_out; + bool valid = true; + size_t end_point1_other_chain_id = 0; + size_t end_point2_other_chain_id = 0; + if (end_point2.chain_id > 0) { + // The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one. + valid = false; + } else { + // End points of the opposite ends of the segments. + end_point1_other_chain_id = equivalent_chain(end_points[(&end_point1 - &end_points.front()) ^ 1].chain_id); + end_point2_other_chain_id = equivalent_chain(end_points[(&end_point2 - &end_points.front()) ^ 1].chain_id); + if (end_point1_other_chain_id == end_point2_other_chain_id && end_point1_other_chain_id != 0) + // This edge forms a loop. Update end_point1 and try another one. + valid = false; + } + if (valid) { + // Remove the first and second point from the queue. + queue.pop(); + queue.remove(end_point2.heap_idx); + assert(end_point1.edge_out = &end_point2); + end_point2.edge_out = &end_point1; + end_point2.distance_out = end_point1.distance_out; + // Assign chain IDs to the newly connected end points, set equivalent_chain if two chains were merged. + size_t chain_id = + (end_point1_other_chain_id == 0) ? + ((end_point2_other_chain_id == 0) ? equivalent_chain.next() : end_point2_other_chain_id) : + ((end_point2_other_chain_id == 0) ? end_point1_other_chain_id : + (end_point1_other_chain_id == end_point2_other_chain_id) ? + end_point1_other_chain_id : + equivalent_chain.merge(end_point1_other_chain_id, end_point2_other_chain_id)); + end_point1.chain_id = chain_id; + end_point2.chain_id = chain_id; + assert(validate_graph_and_queue()); + if (iter == 0) { + // Last iteration. There shall be exactly one or two end points waiting to be connected. + assert(queue.size() == ((first_point == nullptr) ? 2 : 1)); + if (first_point == nullptr) { + first_point = queue.top(); + queue.pop(); + first_point->edge_out = nullptr; + } + last_point = queue.top(); + last_point->edge_out = nullptr; + queue.pop(); + assert(queue.empty()); + break; + } + } else { + // This edge forms a loop. Update end_point1 and try another one. + ++ iter; + end_point1.edge_out = nullptr; + // Update edge_out and distance. + size_t this_idx = &end_point1 - &end_points.front(); + // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda). + size_t next_idx = find_closest_point(kdtree, end_point1.pos, [&end_points, &equivalent_chain, this_idx](size_t idx) { + assert(end_points[this_idx].edge_out == nullptr); + assert(end_points[this_idx].chain_id == 0); + if ((idx ^ this_idx) <= 1 || end_points[idx].chain_id != 0) + // Points of the same segment shall not be connected, + // cannot connect to an already connected point (ideally those would be removed from the KD tree, but the update is difficult). + return false; + size_t chain1 = equivalent_chain(end_points[this_idx ^ 1].chain_id); + size_t chain2 = equivalent_chain(end_points[idx ^ 1].chain_id); + return chain1 != chain2 || chain1 == 0; + }); + assert(next_idx < end_points.size()); + end_point1.edge_out = &end_points[next_idx]; + end_point1.distance_out = (end_points[next_idx].pos - end_point1.pos).squaredNorm(); + // Update position of this end point in the queue based on the distance calculated at the line above. + queue.update(end_point1.heap_idx); + //FIXME Remove the other end point from the KD tree. + // As the KD tree update is expensive, do it only after some larger number of points is removed from the queue. + assert(validate_graph_and_queue()); + } + } + assert(queue.empty()); + + // Now interconnect pairs of segments into a chain. + assert(first_point != nullptr); + out.reserve(num_segments); + bool failed = false; + do { + assert(out.size() < num_segments); + size_t first_point_id = first_point - &end_points.front(); + size_t segment_id = first_point_id >> 1; + bool reverse = (first_point_id & 1) != 0; + EndPoint *second_point = &end_points[first_point_id ^ 1]; + if (REVERSE_COULD_FAIL) { + if (reverse && ! could_reverse_func(segment_id)) { + failed = true; + break; + } + } else { + assert(! reverse || could_reverse_func(segment_id)); + } + out.emplace_back(segment_id, reverse); + first_point = second_point->edge_out; + } while (first_point != nullptr); + if (REVERSE_COULD_FAIL) { + if (failed) { + if (start_near == nullptr) { + // We may try the reverse order. + out.clear(); + first_point = last_point; + failed = false; + do { + assert(out.size() < num_segments); + size_t first_point_id = first_point - &end_points.front(); + size_t segment_id = first_point_id >> 1; + bool reverse = (first_point_id & 1) != 0; + EndPoint *second_point = &end_points[first_point_id ^ 1]; + if (reverse && ! could_reverse_func(segment_id)) { + failed = true; + break; + } + out.emplace_back(segment_id, reverse); + first_point = second_point->edge_out; + } while (first_point != nullptr); + } + } + if (failed) + // As a last resort, try a dumb algorithm, which is not sensitive to edge reversal constraints. + out = chain_segments_closest_point(end_points, kdtree, could_reverse_func, (initial_point != nullptr) ? *initial_point : end_points.front()); + } else { + assert(! failed); + } + } + + assert(out.size() == num_segments); + return out; +} + +template +std::vector> chain_segments_greedy_constrained_reversals(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near) +{ + return chain_segments_greedy_constrained_reversals_(end_point_func, could_reverse_func, num_segments, start_near); +} + +template +std::vector> chain_segments_greedy(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near) +{ + auto could_reverse_func = [](size_t /* idx */) -> bool { return true; }; + return chain_segments_greedy_constrained_reversals_(end_point_func, could_reverse_func, num_segments, start_near); +} + +std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near) +{ + auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; + auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; + std::vector> out = chain_segments_greedy_constrained_reversals(segment_end_point, could_reverse, entities.size(), start_near); + for (size_t i = 0; i < entities.size(); ++ i) { + ExtrusionEntity *ee = entities[i]; + if (ee->is_loop()) + // Ignore reversals for loops, as the start point equals the end point. + out[i].second = false; + // Is can_reverse() respected by the reversals? + assert(entities[i]->can_reverse() || ! out[i].second); + } + return out; +} + +void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain) +{ + assert(entities.size() == chain.size()); + std::vector out; + out.reserve(entities.size()); + for (const std::pair &idx : chain) { + assert(entities[idx.first] != nullptr); + out.emplace_back(entities[idx.first]); + if (idx.second) + out.back()->reverse(); + } + entities.swap(out); +} + +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near) +{ + reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); +} + +std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) +{ + auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); }; + return chain_segments_greedy(segment_end_point, extrusion_paths.size(), start_near); +} + +void reorder_extrusion_paths(std::vector &extrusion_paths, const std::vector> &chain) +{ + assert(extrusion_paths.size() == chain.size()); + std::vector out; + out.reserve(extrusion_paths.size()); + for (const std::pair &idx : chain) { + out.emplace_back(std::move(extrusion_paths[idx.first])); + if (idx.second) + out.back().reverse(); + } + extrusion_paths.swap(out); +} + +void chain_and_reorder_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) +{ + reorder_extrusion_paths(extrusion_paths, chain_extrusion_paths(extrusion_paths, start_near)); +} + +std::vector chain_points(const Points &points, Point *start_near) +{ + auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; }; + std::vector> ordered = chain_segments_greedy(segment_end_point, points.size(), start_near); + std::vector out; + out.reserve(ordered.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(segment_and_reversal.first); + return out; +} + +Polylines chain_polylines(Polylines &&polylines, const Point *start_near) +{ + auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); }; + std::vector> ordered = chain_segments_greedy(segment_end_point, polylines.size(), start_near); + Polylines out; + out.reserve(polylines.size()); + for (auto &segment_and_reversal : ordered) { + out.emplace_back(std::move(polylines[segment_and_reversal.first])); + if (segment_and_reversal.second) + out.back().reverse(); + } + return out; +} + +template static inline T chain_path_items(const Points &points, const T &items) +{ + auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; }; + std::vector> ordered = chain_segments_greedy(segment_end_point, points.size(), nullptr); + T out; + out.reserve(items.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(items[segment_and_reversal.first]); + return out; +} + +ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const ClipperLib::PolyNodes &items) +{ + return chain_path_items(points, items); +} + +std::vector> chain_print_object_instances(const Print &print) +{ + // Order objects using a nearest neighbor search. + Points object_reference_points; + std::vector> instances; + for (size_t i = 0; i < print.objects().size(); ++ i) { + const PrintObject &object = *print.objects()[i]; + for (size_t j = 0; j < object.copies().size(); ++ j) { + object_reference_points.emplace_back(object.copy_center(j)); + instances.emplace_back(i, j); + } + } + auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; }; + std::vector> ordered = chain_segments_greedy(segment_end_point, instances.size(), nullptr); + std::vector> out; + out.reserve(instances.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(instances[segment_and_reversal.first]); + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp new file mode 100644 index 0000000000..cd342015d7 --- /dev/null +++ b/src/libslic3r/ShortestPath.hpp @@ -0,0 +1,38 @@ +#ifndef slic3r_ShortestPath_hpp_ +#define slic3r_ShortestPath_hpp_ + +#include "libslic3r.h" +#include "ExtrusionEntity.hpp" +#include "Point.hpp" + +#include +#include + +namespace ClipperLib { class PolyNode; } + +namespace Slic3r { + +std::vector chain_points(const Points &points, Point *start_near = nullptr); + +std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); +void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain); +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); + +std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); +void reorder_extrusion_paths(std::vector &extrusion_paths, std::vector> &chain); +void chain_and_reorder_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); + +Polylines chain_polylines(Polylines &&src, const Point *start_near = nullptr); +inline Polylines chain_polylines(const Polylines& src, const Point* start_near = nullptr) { Polylines tmp(src); return chain_polylines(std::move(tmp), start_near); } + +std::vector chain_clipper_polynodes(const Points &points, const std::vector &items); + +// Chain instances of print objects by an approximate shortest path. +// Returns pairs of PrintObject idx and instance of that PrintObject. +class Print; +std::vector> chain_print_object_instances(const Print &print); + + +} // namespace Slic3r + +#endif /* slic3r_ShortestPath_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index c0c8e9ea0a..179b35f59e 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -445,8 +445,8 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t Polygons collect_slices_outer(const Layer &layer) { Polygons out; - out.reserve(out.size() + layer.slices.expolygons.size()); - for (const ExPolygon &expoly : layer.slices.expolygons) + out.reserve(out.size() + layer.slices.size()); + for (const ExPolygon &expoly : layer.slices) out.emplace_back(expoly.contour); return out; } @@ -907,9 +907,13 @@ namespace SupportMaterialInternal { polyline.extend_start(fw); polyline.extend_end(fw); // Is the straight perimeter segment supported at both sides? - if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) - // Offset a polyline into a thick line. - polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); + for (size_t i = 0; i < lower_layer.slices.size(); ++ i) + if (lower_layer.slices_bboxes[i].contains(polyline.first_point()) && lower_layer.slices_bboxes[i].contains(polyline.last_point()) && + lower_layer.slices[i].contains(polyline.first_point()) && lower_layer.slices[i].contains(polyline.last_point())) { + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); + break; + } } bridges = union_(bridges); } @@ -923,7 +927,7 @@ namespace SupportMaterialInternal { //FIXME add supports at regular intervals to support long bridges! bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. contact_polygons = diff(contact_polygons, bridges, true); } @@ -994,7 +998,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // inflate the polygons over and over. Polygons &covered = buildplate_covered[layer_id]; covered = buildplate_covered[layer_id - 1]; - polygons_append(covered, offset(lower_layer.slices.expolygons, scale_(0.01))); + polygons_append(covered, offset(lower_layer.slices, scale_(0.01))); covered = union_(covered, false); // don't apply the safety offset. } } @@ -1023,7 +1027,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ Polygons contact_polygons; Polygons slices_margin_cached; float slices_margin_cached_offset = -1.; - Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons); + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices); // Offset of the lower layer, to trim the support polygons with to calculate dense supports. float no_interface_offset = 0.f; if (layer_id == 0) { @@ -1162,7 +1166,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ slices_margin_cached_offset = slices_margin_offset; slices_margin_cached = (slices_margin_offset == 0.f) ? lower_layer_polygons : - offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); + offset2(to_polygons(lower_layer.slices), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! buildplate_covered.empty()) { // Trim the inflated contact surfaces by the top surfaces as well. polygons_append(slices_margin_cached, buildplate_covered[layer_id]); @@ -1468,7 +1472,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta svg.draw(union_ex(top, false), "blue", 0.5f); svg.draw(union_ex(projection_raw, true), "red", 0.5f); svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f)); - svg.draw(layer.slices.expolygons, "green", 0.5f); + svg.draw(layer.slices, "green", 0.5f); } #endif /* SLIC3R_DEBUG */ @@ -1568,8 +1572,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta Polygons &layer_support_area = layer_support_areas[layer_id]; task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area, layer_id] { // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. - // Polygons trimming = union_(to_polygons(layer.slices.expolygons), touching, true); - Polygons trimming = offset(layer.slices.expolygons, float(SCALED_EPSILON)); + // Polygons trimming = union_(to_polygons(layer.slices), touching, true); + Polygons trimming = offset(layer.slices, float(SCALED_EPSILON)); projection = diff(projection_raw, trimming, false); #ifdef SLIC3R_DEBUG { @@ -2101,7 +2105,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( const Layer &object_layer = *object.layers()[i]; if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) break; - polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + polygons_append(polygons_trimming, offset(object_layer.slices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } if (! m_slicing_params.soluble_interface) { // Collect all bottom surfaces, which will be extruded with a bridging flow. @@ -2214,7 +2218,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Expand the bases of the support columns in the 1st layer. columns_base->polygons = diff( offset(columns_base->polygons, inflate_factor_1st_layer), - offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(m_object->layers().front()->slices, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (contacts != nullptr) columns_base->polygons = diff(columns_base->polygons, interface_polygons); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 51d0920946..5d0a7592cd 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -32,4 +32,14 @@ #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) +//==================== +// 2.2.0.alpha1 techs +//==================== +#define ENABLE_2_2_0_ALPHA1 1 + +// Enable thumbnail generator +#define ENABLE_THUMBNAIL_GENERATOR (1 && ENABLE_2_2_0_ALPHA1) +#define ENABLE_THUMBNAIL_GENERATOR_DEBUG (0 && ENABLE_THUMBNAIL_GENERATOR) +#define ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE (1 && ENABLE_THUMBNAIL_GENERATOR) + #endif // _technologies_h_ diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index 02e86eb332..2dbe6caa10 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -10,12 +10,15 @@ namespace Slic3r { class ExPolygon; typedef std::vector ExPolygons; -extern std::vector triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false); -extern std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false); -extern std::vector triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false); -extern std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false); -extern std::vector triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false); -extern std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false); +const bool constexpr NORMALS_UP = false; +const bool constexpr NORMALS_DOWN = true; + +extern std::vector triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygon_2d (const ExPolygon &poly, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygon_2f (const ExPolygon &poly, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP); } // namespace Slic3r diff --git a/src/libslic3r/Time.cpp b/src/libslic3r/Time.cpp index 1f65189b8f..8faa14ade3 100644 --- a/src/libslic3r/Time.cpp +++ b/src/libslic3r/Time.cpp @@ -3,116 +3,232 @@ #include #include #include +#include +#include +#include -//#include -//#include +#ifdef _MSC_VER +#include +#endif - -#ifdef WIN32 - #define WIN32_LEAN_AND_MEAN - #include - #undef WIN32_LEAN_AND_MEAN -#endif /* WIN32 */ +// #include "libslic3r/Utils.hpp" namespace Slic3r { namespace Utils { -namespace { +// "YYYY-MM-DD at HH:MM::SS [UTC]" +// If TimeZone::utc is used with the conversion functions, it will append the +// UTC letters to the end. +static const constexpr char *const SLICER_UTC_TIME_FMT = "%Y-%m-%d at %T"; -// FIXME: after we switch to gcc > 4.9 on the build server, please remove me -#if defined(__GNUC__) && __GNUC__ <= 4 -std::string put_time(const std::tm *tm, const char *fmt) +// ISO8601Z representation of time, without time zone info +static const constexpr char *const ISO8601Z_TIME_FMT = "%Y%m%dT%H%M%SZ"; + +static const char * get_fmtstr(TimeFormat fmt) { - static const constexpr int MAX_CHARS = 200; - char out[MAX_CHARS]; - std::strftime(out, MAX_CHARS, fmt, tm); - return out; + switch (fmt) { + case TimeFormat::gcode: return SLICER_UTC_TIME_FMT; + case TimeFormat::iso8601Z: return ISO8601Z_TIME_FMT; + } + + return ""; } -#else -auto put_time(const std::tm *tm, const char *fmt) -> decltype (std::put_time(tm, fmt)) + +namespace __get_put_time_emulation { +// FIXME: Implementations with the cpp11 put_time and get_time either not +// compile or do not pass the tests on the build server. If we switch to newer +// compilers, this namespace can be deleted with all its content. + +#ifdef _MSC_VER +// VS2019 implementation fails with ISO8601Z_TIME_FMT. +// VS2019 does not have std::strptime either. See bug: +// https://developercommunity.visualstudio.com/content/problem/140618/c-stdget-time-not-parsing-correctly.html + +static const std::map sscanf_fmt_map = { + {SLICER_UTC_TIME_FMT, "%04d-%02d-%02d at %02d:%02d:%02d"}, + {std::string(SLICER_UTC_TIME_FMT) + " UTC", "%04d-%02d-%02d at %02d:%02d:%02d UTC"}, + {ISO8601Z_TIME_FMT, "%04d%02d%02dT%02d%02d%02dZ"} +}; + +static const char * strptime(const char *str, const char *const fmt, std::tm *tms) { - return std::put_time(tm, fmt); + auto it = sscanf_fmt_map.find(fmt); + if (it == sscanf_fmt_map.end()) return nullptr; + + int y, M, d, h, m, s; + if (sscanf(str, it->second.c_str(), &y, &M, &d, &h, &m, &s) != 6) + return nullptr; + + tms->tm_year = y - 1900; // Year since 1900 + tms->tm_mon = M - 1; // 0-11 + tms->tm_mday = d; // 1-31 + tms->tm_hour = h; // 0-23 + tms->tm_min = m; // 0-59 + tms->tm_sec = s; // 0-61 (0-60 in C++11) + + return str; // WARN strptime return val should point after the parsed string } #endif +template +struct GetPutTimeReturnT { + Ttm *tms; + const char *fmt; + GetPutTimeReturnT(Ttm *_tms, const char *_fmt): tms(_tms), fmt(_fmt) {} +}; + +using GetTimeReturnT = GetPutTimeReturnT; +using PutTimeReturnT = GetPutTimeReturnT; + +std::ostream &operator<<(std::ostream &stream, PutTimeReturnT &&pt) +{ + static const constexpr int MAX_CHARS = 200; + char _out[MAX_CHARS]; + strftime(_out, MAX_CHARS, pt.fmt, pt.tms); + stream << _out; + return stream; } -time_t parse_time_ISO8601Z(const std::string &sdate) +inline PutTimeReturnT put_time(const std::tm *tms, const char *fmt) { - int y, M, d, h, m, s; - if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6) - return time_t(-1); - struct tm tms; - tms.tm_year = y - 1900; // Year since 1900 - tms.tm_mon = M - 1; // 0-11 - tms.tm_mday = d; // 1-31 - tms.tm_hour = h; // 0-23 - tms.tm_min = m; // 0-59 - tms.tm_sec = s; // 0-61 (0-60 in C++11) + return {tms, fmt}; +} + +std::istream &operator>>(std::istream &stream, GetTimeReturnT &>) +{ + std::string line; + std::getline(stream, line); + + if (strptime(line.c_str(), gt.fmt, gt.tms) == nullptr) + stream.setstate(std::ios::failbit); + + return stream; +} + +inline GetTimeReturnT get_time(std::tm *tms, const char *fmt) +{ + return {tms, fmt}; +} + +} + +namespace { + +// Platform independent versions of gmtime and localtime. Completely thread +// safe only on Linux. MSVC gtime_s and localtime_s sets global errno thus not +// thread safe. +struct std::tm * _gmtime_r(const time_t *timep, struct tm *result) +{ + assert(timep != nullptr && result != nullptr); #ifdef WIN32 - return _mkgmtime(&tms); + time_t t = *timep; + gmtime_s(result, &t); + return result; +#else + return gmtime_r(timep, result); +#endif +} + +struct std::tm * _localtime_r(const time_t *timep, struct tm *result) +{ + assert(timep != nullptr && result != nullptr); +#ifdef WIN32 + // Converts a time_t time value to a tm structure, and corrects for the + // local time zone. + time_t t = *timep; + localtime_s(result, &t); + return result; +#else + return localtime_r(timep, result); +#endif +} + +time_t _mktime(const struct std::tm *tms) +{ + assert(tms != nullptr); + std::tm _tms = *tms; + return mktime(&_tms); +} + +time_t _timegm(const struct std::tm *tms) +{ + std::tm _tms = *tms; +#ifdef WIN32 + return _mkgmtime(&_tms); #else /* WIN32 */ - return timegm(&tms); + return timegm(&_tms); #endif /* WIN32 */ } -std::string format_time_ISO8601Z(time_t time) +std::string process_format(const char *fmt, TimeZone zone) { - struct tm tms; -#ifdef WIN32 - gmtime_s(&tms, &time); -#else - gmtime_r(&time, &tms); -#endif - char buf[128]; - sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ", - tms.tm_year + 1900, - tms.tm_mon + 1, - tms.tm_mday, - tms.tm_hour, - tms.tm_min, - tms.tm_sec); - return buf; + std::string fmtstr(fmt); + + if (fmtstr == SLICER_UTC_TIME_FMT && zone == TimeZone::utc) + fmtstr += " UTC"; + + return fmtstr; } -std::string format_local_date_time(time_t time) -{ - struct tm tms; -#ifdef WIN32 - // Converts a time_t time value to a tm structure, and corrects for the local time zone. - localtime_s(&tms, &time); -#else - localtime_r(&time, &tms); -#endif - char buf[80]; - strftime(buf, 80, "%x %X", &tms); - return buf; -} +} // namespace time_t get_current_time_utc() -{ +{ using clk = std::chrono::system_clock; return clk::to_time_t(clk::now()); } -static std::string tm2str(const std::tm *tm, const char *fmt) +static std::string tm2str(const std::tm *tms, const char *fmt) { std::stringstream ss; - ss << put_time(tm, fmt); + ss.imbue(std::locale("C")); + ss << __get_put_time_emulation::put_time(tms, fmt); return ss.str(); } -std::string time2str(const time_t &t, TimeZone zone, const char *fmt) +std::string time2str(const time_t &t, TimeZone zone, TimeFormat fmt) { std::string ret; - + std::tm tms = {}; + tms.tm_isdst = -1; + std::string fmtstr = process_format(get_fmtstr(fmt), zone); + switch (zone) { - case TimeZone::local: ret = tm2str(std::localtime(&t), fmt); break; - case TimeZone::utc: ret = tm2str(std::gmtime(&t), fmt) + " UTC"; break; + case TimeZone::local: + ret = tm2str(_localtime_r(&t, &tms), fmtstr.c_str()); break; + case TimeZone::utc: + ret = tm2str(_gmtime_r(&t, &tms), fmtstr.c_str()); break; } - + return ret; } +static time_t str2time(std::istream &stream, TimeZone zone, const char *fmt) +{ + std::tm tms = {}; + tms.tm_isdst = -1; + + stream >> __get_put_time_emulation::get_time(&tms, fmt); + time_t ret = time_t(-1); + + switch (zone) { + case TimeZone::local: ret = _mktime(&tms); break; + case TimeZone::utc: ret = _timegm(&tms); break; + } + + if (stream.fail() || ret < time_t(0)) ret = time_t(-1); + + return ret; +} + +time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt) +{ + std::string fmtstr = process_format(get_fmtstr(fmt), zone).c_str(); + std::stringstream ss(str); + + ss.imbue(std::locale("C")); + return str2time(ss, zone, fmtstr.c_str()); +} + }; // namespace Utils }; // namespace Slic3r diff --git a/src/libslic3r/Time.hpp b/src/libslic3r/Time.hpp index b314e47f72..c03251986b 100644 --- a/src/libslic3r/Time.hpp +++ b/src/libslic3r/Time.hpp @@ -7,41 +7,61 @@ namespace Slic3r { namespace Utils { -// Utilities to convert an UTC time_t to/from an ISO8601 time format, -// useful for putting timestamps into file and directory names. -// Returns (time_t)-1 on error. -time_t parse_time_ISO8601Z(const std::string &s); -std::string format_time_ISO8601Z(time_t time); - -// Format the date and time from an UTC time according to the active locales and a local time zone. -// TODO: make sure time2str is a suitable replacement -std::string format_local_date_time(time_t time); - -// There is no gmtime() on windows. +// Should be thread safe. time_t get_current_time_utc(); -const constexpr char *const SLIC3R_TIME_FMT = "%Y-%m-%d at %T"; - enum class TimeZone { local, utc }; +enum class TimeFormat { gcode, iso8601Z }; -std::string time2str(const time_t &t, TimeZone zone, const char *fmt = SLIC3R_TIME_FMT); +// time_t to string functions... -inline std::string current_time2str(TimeZone zone, const char *fmt = SLIC3R_TIME_FMT) +std::string time2str(const time_t &t, TimeZone zone, TimeFormat fmt); + +inline std::string time2str(TimeZone zone, TimeFormat fmt) { return time2str(get_current_time_utc(), zone, fmt); } -inline std::string current_local_time2str(const char * fmt = SLIC3R_TIME_FMT) +inline std::string utc_timestamp(time_t t) { - return current_time2str(TimeZone::local, fmt); + return time2str(t, TimeZone::utc, TimeFormat::gcode); } -inline std::string current_utc_time2str(const char * fmt = SLIC3R_TIME_FMT) +inline std::string utc_timestamp() { - return current_time2str(TimeZone::utc, fmt); + return utc_timestamp(get_current_time_utc()); } -}; // namespace Utils -}; // namespace Slic3r +// String to time_t function. Returns time_t(-1) if fails to parse the input. +time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt); + + +// ///////////////////////////////////////////////////////////////////////////// +// Utilities to convert an UTC time_t to/from an ISO8601 time format, +// useful for putting timestamps into file and directory names. +// Returns (time_t)-1 on error. + +// Use these functions to convert safely to and from the ISO8601 format on +// all platforms + +inline std::string iso_utc_timestamp(time_t t) +{ + return time2str(t, TimeZone::utc, TimeFormat::iso8601Z); +} + +inline std::string iso_utc_timestamp() +{ + return iso_utc_timestamp(get_current_time_utc()); +} + +inline time_t parse_iso_utc_timestamp(const std::string &str) +{ + return str2time(str, TimeZone::utc, TimeFormat::iso8601Z); +} + +// ///////////////////////////////////////////////////////////////////////////// + +} // namespace Utils +} // namespace Slic3r #endif /* slic3r_Utils_Time_hpp_ */ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index c2fcb11bd2..5cd97522d9 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -236,7 +236,7 @@ bool TriangleMesh::needed_repair() const || this->stl.stats.backwards_edges > 0; } -void TriangleMesh::WriteOBJFile(const char* output_file) +void TriangleMesh::WriteOBJFile(const char* output_file) const { its_write_obj(this->its, output_file); } @@ -593,6 +593,16 @@ TriangleMesh TriangleMesh::convex_hull_3d() const return output_mesh; } +std::vector TriangleMesh::slice(const std::vector &z) +{ + // convert doubles to floats + std::vector z_f(z.begin(), z.end()); + TriangleMeshSlicer mslicer(this); + std::vector layers; + mslicer.slice(z_f, 0.0004f, &layers, [](){}); + return layers; +} + void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; @@ -1861,7 +1871,8 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) } // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. -TriangleMesh make_cube(double x, double y, double z) { +TriangleMesh make_cube(double x, double y, double z) +{ Vec3d pv[8] = { Vec3d(x, y, 0), Vec3d(x, 0, 0), Vec3d(0, 0, 0), Vec3d(0, y, 0), Vec3d(x, y, z), Vec3d(0, y, z), @@ -1878,7 +1889,8 @@ TriangleMesh make_cube(double x, double y, double z) { Pointf3s vertices(&pv[0], &pv[0]+8); TriangleMesh mesh(vertices ,facets); - return mesh; + mesh.repair(); + return mesh; } // Generate the mesh for a cylinder and return it, using @@ -1922,7 +1934,9 @@ TriangleMesh make_cylinder(double r, double h, double fa) facets.emplace_back(Vec3crd(id, 2, 3)); facets.emplace_back(Vec3crd(id, id - 1, 2)); - return TriangleMesh(std::move(vertices), std::move(facets)); + TriangleMesh mesh(std::move(vertices), std::move(facets)); + mesh.repair(); + return mesh; } // Generates mesh for a sphere centered about the origin, using the generated angle @@ -1978,7 +1992,9 @@ TriangleMesh make_sphere(double radius, double fa) k2 = k2_next; } } - return TriangleMesh(std::move(vertices), std::move(facets)); + TriangleMesh mesh(std::move(vertices), std::move(facets)); + mesh.repair(); + return mesh; } } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 81390b79b0..9c9f82040d 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -31,7 +31,7 @@ public: float volume(); void check_topology(); bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } - void WriteOBJFile(const char* output_file); + void WriteOBJFile(const char* output_file) const; void scale(float factor); void scale(const Vec3d &versor); void translate(float x, float y, float z); @@ -58,8 +58,14 @@ public: BoundingBoxf3 bounding_box() const; // Returns the bbox of this TriangleMesh transformed by the given transformation BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const; + // Return the size of the mesh in coordinates. + Vec3d size() const { return stl.stats.size.cast(); } + /// Return the center of the related bounding box. + Vec3d center() const { return this->bounding_box().center(); } // Returns the convex hull of this TriangleMesh TriangleMesh convex_hull_3d() const; + // Slice this mesh at the provided Z levels and return the vector + std::vector slice(const std::vector& z); void reset_repair_stats(); bool needed_repair() const; void require_shared_vertices(); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 5d847573db..9af2adcc62 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "libslic3r.h" diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 348be49ccb..a5b53584d7 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -217,4 +217,9 @@ void Zipper::finalize() m_impl->blow_up(); } +const std::string &Zipper::get_filename() const +{ + return m_impl->m_zipname; +} + } diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index a574de9596..be1e69b5c3 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -83,6 +83,8 @@ public: void finish_entry(); void finalize(); + + const std::string & get_filename() const; }; diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 895efdb4d4..678ad9ed28 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -424,14 +424,19 @@ int copy_file(const std::string &from, const std::string &to) static const auto perms = boost::filesystem::owner_read | boost::filesystem::owner_write | boost::filesystem::group_read | boost::filesystem::others_read; // aka 644 // Make sure the file has correct permission both before and after we copy over it. - try { - if (boost::filesystem::exists(target)) - boost::filesystem::permissions(target, perms); - boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists); - boost::filesystem::permissions(target, perms); - } catch (std::exception & /* ex */) { + // NOTE: error_code variants are used here to supress expception throwing. + // Error code of permission() calls is ignored on purpose - if they fail, + // the copy_file() function will fail appropriately and we don't want the permission() + // calls to cause needless failures on permissionless filesystems (ie. FATs on SD cards etc.) + // or when the target file doesn't exist. + boost::system::error_code ec; + boost::filesystem::permissions(target, perms, ec); + boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); + if (ec) { return -1; } + boost::filesystem::permissions(target, perms, ec); + return 0; } @@ -543,7 +548,7 @@ std::string string_printf(const char *format, ...) std::string header_slic3r_generated() { - return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::current_utc_time2str(); + return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp(); } unsigned get_current_pid() diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index 9ca0bdff23..ab9aba9afa 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -18,11 +18,13 @@ if(Qhull_FOUND) message(STATUS "Using qhull from system.") if(SLIC3R_STATIC) + slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhullstatic_r" RelWithDebInfo Release) target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r) else() + slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhull_r" RelWithDebInfo Release) target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r) endif() - + else(Qhull_FOUND) project(qhull) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 17b76e6296..9ed7424c05 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -144,6 +144,8 @@ set(SLIC3R_GUI_SOURCES Utils/OctoPrint.hpp Utils/Duet.cpp Utils/Duet.hpp + Utils/FlashAir.cpp + Utils/FlashAir.hpp Utils/PrintHost.cpp Utils/PrintHost.hpp Utils/Bonjour.cpp @@ -154,6 +156,7 @@ set(SLIC3R_GUI_SOURCES Utils/UndoRedo.hpp Utils/HexFile.cpp Utils/HexFile.hpp + Utils/Thread.hpp ) if (APPLE) diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 80b6521b6f..fa756f49da 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -66,7 +66,7 @@ void Snapshot::load_ini(const std::string &path) if (kvp.first == "id") this->id = kvp.second.data(); else if (kvp.first == "time_captured") { - this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data()); + this->time_captured = Slic3r::Utils::parse_iso_utc_timestamp(kvp.second.data()); if (this->time_captured == (time_t)-1) throw_on_parse_error("invalid timestamp"); } else if (kvp.first == "slic3r_version_captured") { @@ -165,7 +165,7 @@ void Snapshot::save_ini(const std::string &path) // Export the common "snapshot". c << std::endl << "[snapshot]" << std::endl; c << "id = " << this->id << std::endl; - c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl; + c << "time_captured = " << Slic3r::Utils::iso_utc_timestamp(this->time_captured) << std::endl; c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl; c << "comment = " << this->comment << std::endl; c << "reason = " << reason_string(this->reason) << std::endl; @@ -365,7 +365,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: Snapshot snapshot; // Snapshot header. snapshot.time_captured = Slic3r::Utils::get_current_time_utc(); - snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured); + snapshot.id = Slic3r::Utils::iso_utc_timestamp(snapshot.time_captured); snapshot.slic3r_version_captured = Slic3r::SEMVER; snapshot.comment = comment; snapshot.reason = reason; @@ -393,9 +393,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: // Read the active config bundle, parse the config version. PresetBundle bundle; bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); - for (const VendorProfile &vp : bundle.vendors) - if (vp.id == cfg.name) - cfg.version.config_version = vp.config_version; + for (const auto &vp : bundle.vendors) + if (vp.second.id == cfg.name) + cfg.version.config_version = vp.second.config_version; // Fill-in the min/max slic3r version from the config index, if possible. try { // Load the config index for the vendor. diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 3f8f960f10..da522dd5e5 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -235,9 +235,9 @@ size_t Index::load(const boost::filesystem::path &path) value = left_trim(value + 1); *key_end = 0; boost::optional semver; - if (maybe_semver) + if (maybe_semver) semver = Semver::parse(key); - if (key_value_pair) { + if (key_value_pair) { if (semver) throw file_parser_error("Key cannot be a semantic version", path, idx_line);\ // Verify validity of the key / value pair. @@ -288,7 +288,6 @@ Index::const_iterator Index::find(const Semver &ver) const Index::const_iterator Index::recommended() const { - int idx = -1; const_iterator highest = this->end(); for (const_iterator it = this->begin(); it != this->end(); ++ it) if (it->is_current_slic3r_supported() && diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 8094cdde10..086ba7a74a 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -417,7 +417,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const } bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } -bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); } +bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } std::vector GLVolumeCollection::load_object( const ModelObject *model_object, @@ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary( TriangleMesh convex_hull = mesh.convex_hull_3d(); for (const std::pair& instance_idx : instances) { const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; - this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); + this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh); v.indexed_vertex_array.finalize_geometry(opengl_initialized); @@ -1717,13 +1717,18 @@ static void thick_point_to_verts(const Vec3crd& point, point_to_indexed_vertex_array(point, width, height, volume.indexed_vertex_array); } +void _3DScene::extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume) +{ + if (polyline.size() >= 2) { + size_t num_segments = polyline.size() - 1; + thick_lines_to_verts(polyline.lines(), std::vector(num_segments, width), std::vector(num_segments, height), false, print_z, volume); + } +} + // Fill in the qverts and tverts with quads and triangles for the extrusion_path. void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume) { - Lines lines = extrusion_path.polyline.lines(); - std::vector widths(lines.size(), extrusion_path.width); - std::vector heights(lines.size(), extrusion_path.height); - thick_lines_to_verts(lines, widths, heights, false, print_z, volume); + extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume); } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 8c1d68f77b..8c5040eee2 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -656,6 +656,7 @@ public: static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); + static void extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume); static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 5a165e8aed..6b3f54f3a7 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -28,6 +28,9 @@ static const std::string VENDOR_PREFIX = "vendor:"; static const std::string MODEL_PREFIX = "model:"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; +const std::string AppConfig::SECTION_FILAMENTS = "filaments"; +const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; + void AppConfig::reset() { m_storage.clear(); diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index 8ad17b9db8..97c369ab64 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -80,6 +80,12 @@ public: } } + bool has_section(const std::string §ion) const + { return m_storage.find(section) != m_storage.end(); } + const std::map& get_section(const std::string §ion) const + { return m_storage.find(section)->second; } + void set_section(const std::string §ion, const std::map& data) + { m_storage[section] = data; } void clear_section(const std::string §ion) { m_storage[section].clear(); } @@ -125,6 +131,8 @@ public: std::vector get_recent_projects() const; void set_recent_projects(const std::vector& recent_projects); + static const std::string SECTION_FILAMENTS; + static const std::string SECTION_MATERIALS; private: // Map of section, name -> value std::map> m_storage; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index a1db6884ee..5ab65f340b 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -10,12 +10,19 @@ #include #include +#if ENABLE_THUMBNAIL_GENERATOR +#include +#endif // ENABLE_THUMBNAIL_GENERATOR + // Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "libslic3r/GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/libslic3r.h" #include @@ -55,6 +62,7 @@ bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech) switch (tech) { case ptFFF: m_print = m_fff_print; break; case ptSLA: m_print = m_sla_print; break; + default: assert(false); break; } changed = true; } @@ -82,8 +90,12 @@ void BackgroundSlicingProcess::process_fff() assert(m_print == m_fff_print); m_print->process(); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); - m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); - if (this->set_step_started(bspsGCodeFinalize)) { +#if ENABLE_THUMBNAIL_GENERATOR + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_data); +#else + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); +#endif // ENABLE_THUMBNAIL_GENERATOR + if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. @@ -99,17 +111,46 @@ void BackgroundSlicingProcess::process_fff() m_print->set_status(100, _utf8(L("Slicing complete"))); } this->set_step_done(bspsGCodeFinalize); - } + } } +#if ENABLE_THUMBNAIL_GENERATOR +static void write_thumbnail(Zipper& zipper, const ThumbnailData& data) +{ + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size); + mz_free(png_data); + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR + void BackgroundSlicingProcess::process_sla() { assert(m_print == m_sla_print); m_print->process(); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { - const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); - m_sla_print->export_raster(export_path); + const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); + + Zipper zipper(export_path); + m_sla_print->export_raster(zipper); + +#if ENABLE_THUMBNAIL_GENERATOR + if (m_thumbnail_data != nullptr) + { + for (const ThumbnailData& data : *m_thumbnail_data) + { + if (data.is_valid()) + write_thumbnail(zipper, data); + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR + + zipper.finalize(); + m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); } else if (! m_upload_job.empty()) { prepare_upload(); @@ -220,11 +261,7 @@ bool BackgroundSlicingProcess::start() if (m_state == STATE_INITIAL) { // The worker thread is not running yet. Start it. assert(! m_thread.joinable()); - boost::thread::attributes attrs; - // Duplicating the stack allocation size of Thread Building Block worker threads of the thread pool: - // allocate 4MB on a 64bit system, allocate 2MB on a 32bit system by default. - attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024)); - m_thread = boost::thread(attrs, [this]{this->thread_proc_safe();}); + m_thread = create_thread([this]{this->thread_proc_safe();}); // Wait until the worker thread is ready to execute the background processing task. m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; }); } @@ -417,13 +454,26 @@ void BackgroundSlicingProcess::prepare_upload() throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); } run_post_process_scripts(source_path.string(), m_fff_print->config()); - m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); + m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); } else { - m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string()); - } + m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str()); + Zipper zipper{source_path.string()}; + m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string()); +#if ENABLE_THUMBNAIL_GENERATOR + if (m_thumbnail_data != nullptr) + { + for (const ThumbnailData& data : *m_thumbnail_data) + { + if (data.is_valid()) + write_thumbnail(zipper, data); + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR + zipper.finalize(); + } + + m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str()); m_upload_job.upload_data.source_path = std::move(source_path); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index cf5edd55f9..a603d52acc 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -6,17 +6,20 @@ #include #include -#include #include #include "libslic3r/Print.hpp" #include "slic3r/Utils/PrintHost.hpp" +#include "slic3r/Utils/Thread.hpp" namespace Slic3r { class DynamicPrintConfig; class GCodePreviewData; +#if ENABLE_THUMBNAIL_GENERATOR +struct ThumbnailData; +#endif // ENABLE_THUMBNAIL_GENERATOR class Model; class SLAPrint; @@ -49,6 +52,10 @@ public: void set_fff_print(Print *print) { m_fff_print = print; } void set_sla_print(SLAPrint *print) { m_sla_print = print; } void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } +#if ENABLE_THUMBNAIL_GENERATOR + void set_thumbnail_data(const std::vector* data) { m_thumbnail_data = data; } +#endif // ENABLE_THUMBNAIL_GENERATOR + // The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished // and the background processing will transition into G-code export. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. @@ -151,6 +158,10 @@ private: SLAPrint *m_sla_print = nullptr; // Data structure, to which the G-code export writes its annotations. GCodePreviewData *m_gcode_preview_data = nullptr; +#if ENABLE_THUMBNAIL_GENERATOR + // Data structures, used to write thumbnails into gcode. + const std::vector* m_thumbnail_data = nullptr; +#endif // ENABLE_THUMBNAIL_GENERATOR // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 8e3a6d1f12..9fbabe930d 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -1,7 +1,9 @@ #include "libslic3r/libslic3r.h" #include "Camera.hpp" +#if !ENABLE_THUMBNAIL_GENERATOR #include "3DScene.hpp" +#endif // !ENABLE_THUMBNAIL_GENERATOR #include "GUI_App.hpp" #include "AppConfig.hpp" @@ -22,6 +24,10 @@ namespace Slic3r { namespace GUI { const double Camera::DefaultDistance = 1000.0; +#if ENABLE_THUMBNAIL_GENERATOR +const double Camera::DefaultZoomToBoxMarginFactor = 1.025; +const double Camera::DefaultZoomToVolumesMarginFactor = 1.025; +#endif // ENABLE_THUMBNAIL_GENERATOR double Camera::FrustrumMinZRange = 50.0; double Camera::FrustrumMinNearZ = 100.0; double Camera::FrustrumZMargin = 10.0; @@ -266,10 +272,18 @@ void Camera::apply_projection(const BoundingBoxf3& box) const glsafe(::glMatrixMode(GL_MODELVIEW)); } +#if ENABLE_THUMBNAIL_GENERATOR +void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) +#else void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) +#endif // ENABLE_THUMBNAIL_GENERATOR { // Calculate the zoom factor needed to adjust the view around the given box. +#if ENABLE_THUMBNAIL_GENERATOR + double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor); +#else double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); +#endif // ENABLE_THUMBNAIL_GENERATOR if (zoom > 0.0) { m_zoom = zoom; @@ -278,6 +292,20 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) } } +#if ENABLE_THUMBNAIL_GENERATOR +void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor) +{ + Vec3d center; + double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor); + if (zoom > 0.0) + { + m_zoom = zoom; + // center view around the calculated center + m_target = center; + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR + #if ENABLE_CAMERA_STATISTICS void Camera::debug_render() const { @@ -372,7 +400,11 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo return ret; } +#if ENABLE_THUMBNAIL_GENERATOR +double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const +#else double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const +#endif // ENABLE_THUMBNAIL_GENERATOR { double max_bb_size = box.max_size(); if (max_bb_size == 0.0) @@ -405,13 +437,15 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca double max_x = 0.0; double max_y = 0.0; +#if !ENABLE_THUMBNAIL_GENERATOR // margin factor to give some empty space around the box double margin_factor = 1.25; +#endif // !ENABLE_THUMBNAIL_GENERATOR for (const Vec3d& v : vertices) { // project vertex on the plane perpendicular to camera forward axis - Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2)); + Vec3d pos = v - bb_center; Vec3d proj_on_plane = pos - pos.dot(forward) * forward; // calculates vertex coordinate along camera xy axes @@ -431,6 +465,72 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y)); } +#if ENABLE_THUMBNAIL_GENERATOR +double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const +{ + if (volumes.empty()) + return -1.0; + + // project the volumes vertices on a plane perpendicular to the camera forward axis + // then calculates the vertices coordinate on this plane along the camera xy axes + + // ensure that the view matrix is updated + apply_view_matrix(); + + Vec3d right = get_dir_right(); + Vec3d up = get_dir_up(); + Vec3d forward = get_dir_forward(); + + BoundingBoxf3 box; + for (const GLVolume* volume : volumes) + { + box.merge(volume->transformed_bounding_box()); + } + center = box.center(); + + double min_x = DBL_MAX; + double min_y = DBL_MAX; + double max_x = -DBL_MAX; + double max_y = -DBL_MAX; + + for (const GLVolume* volume : volumes) + { + const Transform3d& transform = volume->world_matrix(); + const TriangleMesh* hull = volume->convex_hull(); + if (hull == nullptr) + continue; + + for (const Vec3f& vertex : hull->its.vertices) + { + Vec3d v = transform * vertex.cast(); + + // project vertex on the plane perpendicular to camera forward axis + Vec3d pos = v - center; + Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + + // calculates vertex coordinate along camera xy axes + double x_on_plane = proj_on_plane.dot(right); + double y_on_plane = proj_on_plane.dot(up); + + min_x = std::min(min_x, x_on_plane); + min_y = std::min(min_y, y_on_plane); + max_x = std::max(max_x, x_on_plane); + max_y = std::max(max_y, y_on_plane); + } + } + + center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up; + + double dx = margin_factor * (max_x - min_x); + double dy = margin_factor * (max_y - min_y); + + if ((dx == 0.0) || (dy == 0.0)) + return -1.0f; + + return std::min((double)canvas_w / dx, (double)canvas_h / dy); +} +#endif // ENABLE_THUMBNAIL_GENERATOR + void Camera::set_distance(double distance) const { m_distance = distance; diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 839d0d6cf1..cb634138f2 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -2,6 +2,9 @@ #define slic3r_Camera_hpp_ #include "libslic3r/BoundingBox.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "3DScene.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include namespace Slic3r { @@ -10,6 +13,10 @@ namespace GUI { struct Camera { static const double DefaultDistance; +#if ENABLE_THUMBNAIL_GENERATOR + static const double DefaultZoomToBoxMarginFactor; + static const double DefaultZoomToVolumesMarginFactor; +#endif // ENABLE_THUMBNAIL_GENERATOR static double FrustrumMinZRange; static double FrustrumMinNearZ; static double FrustrumZMargin; @@ -90,7 +97,12 @@ public: void apply_view_matrix() const; void apply_projection(const BoundingBoxf3& box) const; +#if ENABLE_THUMBNAIL_GENERATOR + void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor); + void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor); +#else void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); +#endif // ENABLE_THUMBNAIL_GENERATOR #if ENABLE_CAMERA_STATISTICS void debug_render() const; @@ -100,7 +112,12 @@ private: // returns tight values for nearZ and farZ plane around the given bounding box // the camera MUST be outside of the bounding box in eye coordinate of the given box std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; +#if ENABLE_THUMBNAIL_GENERATOR + double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const; + double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const; +#else double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; +#endif // ENABLE_THUMBNAIL_GENERATOR void set_distance(double distance) const; }; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index c9cdff1628..f76f752f0e 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -349,16 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) toggle_field("pad_wall_thickness", pad_en); toggle_field("pad_wall_height", pad_en); + toggle_field("pad_brim_size", pad_en); toggle_field("pad_max_merge_distance", pad_en); // toggle_field("pad_edge_radius", supports_en); toggle_field("pad_wall_slope", pad_en); toggle_field("pad_around_object", pad_en); + toggle_field("pad_around_object_everywhere", pad_en); - bool has_suppad = pad_en && supports_en; - bool zero_elev = config->opt_bool("pad_around_object") && has_suppad; + bool zero_elev = config->opt_bool("pad_around_object") && pad_en; toggle_field("support_object_elevation", supports_en && !zero_elev); toggle_field("pad_object_gap", zero_elev); + toggle_field("pad_around_object_everywhere", zero_elev); toggle_field("pad_object_connector_stride", zero_elev); toggle_field("pad_object_connector_width", zero_elev); toggle_field("pad_object_connector_penetration", zero_elev); diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index c89e4895e0..d48dfccc98 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -35,9 +35,14 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5"); text += "\">"; text += ""; + + static const constexpr char *LOCALE_TIME_FMT = "%x %X"; + wxString datetime = wxDateTime(snapshot.time_captured).Format(LOCALE_TIME_FMT); + // Format the row header. - text += wxString("") + (snapshot_active ? _(L("Active")) + ": " : "") + - Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); + text += wxString("") + (snapshot_active ? _(L("Active")) + ": " : "") + + datetime + ": " + format_reason(snapshot.reason); + if (! snapshot.comment.empty()) text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")"; text += "
"; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4e7aff0280..bc73e32625 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,9 +1,12 @@ +// FIXME: extract absolute units -> em + #include "ConfigWizard_private.hpp" #include #include #include #include +#include #include #include #include @@ -19,10 +22,10 @@ #include #include #include +#include #include #include "libslic3r/Utils.hpp" -#include "PresetBundle.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" #include "slic3r/Config/Snapshot.hpp" @@ -37,6 +40,83 @@ using Config::Snapshot; using Config::SnapshotDB; +// Configuration data structures extensions needed for the wizard + +Bundle::Bundle(fs::path source_path, bool is_in_resources, bool is_prusa_bundle) + : preset_bundle(new PresetBundle) + , vendor_profile(nullptr) + , is_in_resources(is_in_resources) + , is_prusa_bundle(is_prusa_bundle) +{ + preset_bundle->load_configbundle(source_path.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + auto first_vendor = preset_bundle->vendors.begin(); + wxCHECK_RET(first_vendor != preset_bundle->vendors.end(), "Failed to load preset bundle"); + vendor_profile = &first_vendor->second; +} + +Bundle::Bundle(Bundle &&other) + : preset_bundle(std::move(other.preset_bundle)) + , vendor_profile(other.vendor_profile) + , is_in_resources(other.is_in_resources) + , is_prusa_bundle(other.is_prusa_bundle) +{ + other.vendor_profile = nullptr; +} + +BundleMap BundleMap::load() +{ + BundleMap res; + + const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + auto prusa_bundle_rsrc = false; + if (! boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_rsrc = true; + } + Bundle prusa_bundle(std::move(prusa_bundle_path), prusa_bundle_rsrc, true); + res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); + + // Load the other bundles in the datadir/vendor directory + // and then additionally from resources/profiles. + bool is_in_resources = false; + for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { + for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { + if (Slic3r::is_ini_file(dir_entry)) { + std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part + + // Don't load this bundle if we've already loaded it. + if (res.find(id) != res.end()) { continue; } + + Bundle bundle(dir_entry.path(), is_in_resources); + res.emplace(std::move(id), std::move(bundle)); + } + } + + is_in_resources = true; + } + + return res; +} + +Bundle& BundleMap::prusa_bundle() +{ + auto it = find(PresetBundle::PRUSA_BUNDLE); + if (it == end()) { + throw std::runtime_error("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + } + + return it->second; +} + +const Bundle& BundleMap::prusa_bundle() const +{ + return const_cast(this)->prusa_bundle(); +} + + // Printer model picker GUI control struct PrinterPickerEvent : public wxEvent @@ -62,7 +142,9 @@ struct PrinterPickerEvent : public wxEvent wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors, const ModelFilter &filter) +const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) : wxPanel(parent) , vendor_id(vendor.id) , width(0) @@ -93,6 +175,17 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt if (wxFileExists(bitmap_file)) { bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); bitmap_width = bitmap.GetWidth(); + } else { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % bitmap_file + % vendor.id + % model.id; + + const wxString placeholder_file = GUI::from_u8(Slic3r::var(PRINTER_PLACEHOLDER)); + if (wxFileExists(placeholder_file)) { + bitmap.LoadFile(placeholder_file, wxBITMAP_TYPE_PNG); + bitmap_width = bitmap.GetWidth(); + } } auto *title = new wxStaticText(this, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); @@ -132,7 +225,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); - bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name); + const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); cbox->SetValue(enabled); variants_sizer->Add(cbox, 0, wxBOTTOM, 3); @@ -210,8 +303,8 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt SetSizer(sizer); } -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors) - : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig_vendors, [](const VendorProfile::PrinterModel&) { return true; }) +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) + : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) {} void PrinterPicker::select_all(bool select, bool alternates) @@ -241,6 +334,19 @@ void PrinterPicker::select_one(size_t i, bool select) } } +bool PrinterPicker::any_selected() const +{ + for (const auto &cb : cboxes) { + if (cb->GetValue()) { return true; } + } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue()) { return true; } + } + + return false; +} + void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) { PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); @@ -279,16 +385,18 @@ ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxStrin ConfigWizardPage::~ConfigWizardPage() {} -void ConfigWizardPage::append_text(wxString text) +wxStaticText* ConfigWizardPage::append_text(wxString text) { auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); widget->Wrap(WRAP_WIDTH); widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); append(widget); + return widget; } void ConfigWizardPage::append_spacer(int space) { + // FIXME: scaling content->AddSpacer(space); } @@ -303,34 +411,42 @@ PageWelcome::PageWelcome(ConfigWizard *parent) _(L("Welcome to the %s Configuration Wizard")) #endif , SLIC3R_APP_NAME), _(L("Welcome"))) - , cbox_reset(nullptr) + , welcome_text(append_text(wxString::Format( + _(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")), + SLIC3R_APP_NAME, + ConfigWizard::name()) + )) + , cbox_reset(append( + new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)"))) + )) { - if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) { - wxString::Format(_(L("Run %s")), ConfigWizard::name()); - append_text(wxString::Format( - _(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")), - SLIC3R_APP_NAME, - ConfigWizard::name()) - ); - } else { - cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)"))); - append(cbox_reset); - } + welcome_text->Hide(); + cbox_reset->Hide(); +} - Show(); +void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) +{ + const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; + welcome_text->Show(data_empty); + cbox_reset->Show(!data_empty); } -PagePrinters::PagePrinters(ConfigWizard *parent, wxString title, wxString shortname, const VendorProfile &vendor, unsigned indent, Technology technology) +PagePrinters::PagePrinters(ConfigWizard *parent, + wxString title, + wxString shortname, + const VendorProfile &vendor, + unsigned indent, + Technology technology) : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) + , technology(technology) + , install(false) // only used for 3rd party vendors { enum { COL_SIZE = 200, }; - bool check_first_variant = technology == T_FFF && wizard_p()->check_first_variant(); - - AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; + AppConfig *appconfig = &this->wizard_p()->appconfig_new; const auto families = vendor.families(); for (const auto &family : families) { @@ -345,16 +461,11 @@ PagePrinters::PagePrinters(ConfigWizard *parent, wxString title, wxString shortn } const auto picker_title = family.empty() ? wxString() : wxString::Format(_(L("%s Family")), family); - auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, appconfig_vendors, filter); + auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); - if (check_first_variant) { - // Select the default (first) model/variant on the Prusa vendor - picker->select_one(0, true); - check_first_variant = false; - } - - picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { - appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { + appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + wizard_p()->on_printer_pick(this, evt); }); append(new wxStaticLine(this)); @@ -377,6 +488,195 @@ int PagePrinters::get_width() const [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); } +bool PagePrinters::any_selected() const +{ + for (const auto *picker : printer_pickers) { + if (picker->any_selected()) { return true; } + } + + return false; +} + +void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) +{ + if (technology == T_FFF + && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY) + && printer_pickers.size() > 0) { + printer_pickers[0]->select_one(0, true); + } +} + + +const std::string PageMaterials::EMPTY; + +PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) + : ConfigWizardPage(parent, std::move(title), std::move(shortname)) + , materials(materials) + , list_l1(new StringList(this)) + , list_l2(new StringList(this)) + , list_l3(new PresetList(this)) +{ + append_spacer(VERTICAL_SPACING); + + const int em = parent->em_unit(); + const int list_h = 30*em; + + list_l1->SetMinSize(wxSize(8*em, list_h)); + list_l2->SetMinSize(wxSize(13*em, list_h)); + list_l3->SetMinSize(wxSize(25*em, list_h)); + + auto *grid = new wxFlexGridSizer(3, 0, em); + + grid->Add(new wxStaticText(this, wxID_ANY, list1name)); + grid->Add(new wxStaticText(this, wxID_ANY, _(L("Vendor:")))); + grid->Add(new wxStaticText(this, wxID_ANY, _(L("Profile:")))); + + grid->Add(list_l1); + grid->Add(list_l2); + grid->Add(list_l3); + + auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *sel_all = new wxButton(this, wxID_ANY, _(L("All"))); + auto *sel_none = new wxButton(this, wxID_ANY, _(L("None"))); + btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); + btn_sizer->Add(sel_none); + + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(btn_sizer, 0, wxALIGN_RIGHT); + + append(grid); + + list_l1->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_l1->GetSelection(), list_l2->GetSelection()); + }); + list_l2->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_l1->GetSelection(), list_l2->GetSelection()); + }); + + list_l3->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); + + sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); + sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); + + reload_presets(); +} + +void PageMaterials::reload_presets() +{ + clear(); + + list_l1->append(_(L("(All)")), &EMPTY); + + for (const std::string &type : materials->types) { + list_l1->append(type, &type); + } + + if (list_l1->GetCount() > 0) { + list_l1->SetSelection(0); + sel1_prev = wxNOT_FOUND; + sel2_prev = wxNOT_FOUND; + update_lists(0, 0); + } + + presets_loaded = true; +} + +void PageMaterials::update_lists(int sel1, int sel2) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + if (sel1 != sel1_prev) { + // Refresh the second list + + // XXX: The vendor list is created with quadratic complexity here, + // but the number of vendors is going to be very small this shouldn't be a problem. + + list_l2->Clear(); + list_l2->append(_(L("(All)")), &EMPTY); + if (sel1 != wxNOT_FOUND) { + const std::string &type = list_l1->get_data(sel1); + + materials->filter_presets(type, EMPTY, [this](const Preset *p) { + const std::string &vendor = this->materials->get_vendor(p); + + if (list_l2->find(vendor) == wxNOT_FOUND) { + list_l2->append(vendor, &vendor); + } + }); + } + + sel1_prev = sel1; + sel2 = 0; + sel2_prev = wxNOT_FOUND; + list_l2->SetSelection(sel2); + list_l3->Clear(); + } + + if (sel2 != sel2_prev) { + // Refresh the third list + + list_l3->Clear(); + if (sel1 != wxNOT_FOUND && sel2 != wxNOT_FOUND) { + const std::string &type = list_l1->get_data(sel1); + const std::string &vendor = list_l2->get_data(sel2); + + materials->filter_presets(type, vendor, [this](const Preset *p) { + const int i = list_l3->append(p->name, p); + const bool checked = wizard_p()->appconfig_new.has(materials->appconfig_section(), p->name); + list_l3->Check(i, checked); + }); + } + + sel2_prev = sel2; + } +} + +void PageMaterials::select_material(int i) +{ + const bool checked = list_l3->IsChecked(i); + const Preset &preset = list_l3->get_data(i); + + if (checked) { + wizard_p()->appconfig_new.set(materials->appconfig_section(), preset.name, "1"); + } else { + wizard_p()->appconfig_new.erase(materials->appconfig_section(), preset.name); + } +} + +void PageMaterials::select_all(bool select) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + for (unsigned i = 0; i < list_l3->GetCount(); i++) { + const bool current = list_l3->IsChecked(i); + if (current != select) { + list_l3->Check(i, select); + select_material(i); + } + } +} + +void PageMaterials::clear() +{ + list_l1->Clear(); + list_l2->Clear(); + list_l3->Clear(); + sel1_prev = wxNOT_FOUND; + sel2_prev = wxNOT_FOUND; + presets_loaded = false; +} + +void PageMaterials::on_activate() +{ + if (! presets_loaded) { + wizard_p()->update_materials(materials->technology); + reload_presets(); + } +} + const char *PageCustom::default_profile_name = "My Settings"; @@ -400,7 +700,7 @@ PageCustom::PageCustom(ConfigWizard *parent) cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { tc_profile_name->Enable(custom_wanted()); - wizard_p()->on_custom_setup(custom_wanted()); + wizard_p()->on_custom_setup(); }); append(cb_custom); @@ -413,7 +713,7 @@ PageUpdate::PageUpdate(ConfigWizard *parent) , version_check(true) , preset_update(true) { - const AppConfig *app_config = GUI::get_app_config(); + const AppConfig *app_config = wxGetApp().app_config; auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); @@ -445,51 +745,73 @@ PageUpdate::PageUpdate(ConfigWizard *parent) box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); } +PageMode::PageMode(ConfigWizard *parent) + : ConfigWizardPage(parent, _(L("View mode")), _(L("View mode"))) +{ + append_text(_(L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" + "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " + "The other two offer progressivly more sophisticated fine-tuning, " + "they are suitable for advanced and expert usiser, respectively."))); + + radio_simple = new wxRadioButton(this, wxID_ANY, _(L("Simple mode"))); + radio_advanced = new wxRadioButton(this, wxID_ANY, _(L("Advanced mode"))); + radio_expert = new wxRadioButton(this, wxID_ANY, _(L("Expert mode"))); + + append(radio_simple); + append(radio_advanced); + append(radio_expert); +} + +void PageMode::on_activate() +{ + std::string mode { "simple" }; + wxGetApp().app_config->get("", "view_mode", mode); + + if (mode == "advanced") { radio_advanced->SetValue(true); } + else if (mode == "expert") { radio_expert->SetValue(true); } + else { radio_simple->SetValue(true); } +} + +void PageMode::serialize_mode(AppConfig *app_config) const +{ + const char *mode = "simple"; + + if (radio_advanced->GetValue()) { mode = "advanced"; } + if (radio_expert->GetValue()) { mode = "expert"; } + + app_config->set("view_mode", mode); +} + PageVendors::PageVendors(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors"))) { - append_text(wxString::Format(_(L("Pick another vendor supported by %s:")), SLIC3R_APP_NAME)); + const AppConfig &appconfig = this->wizard_p()->appconfig_new; + + append_text(wxString::Format(_(L("Pick another vendor supported by %s: (FIXME: this text)")), SLIC3R_APP_NAME)); auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); - AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; - wxArrayString choices_vendors; + for (const auto &pair : wizard_p()->bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - for (const auto vendor_pair : wizard_p()->vendors) { - const auto &vendor = vendor_pair.second; - if (vendor.id == "PrusaResearch") { continue; } - - auto *picker = new PrinterPicker(this, vendor, "", MAX_COLS, appconfig_vendors); - picker->Hide(); - pickers.push_back(picker); - choices_vendors.Add(vendor.name); - - picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { - appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); + cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { + wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); }); - } - auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors); - if (choices_vendors.GetCount() > 0) { - vendor_picker->SetSelection(0); - on_vendor_pick(0); - } + const auto &vendors = appconfig.vendors(); + const bool enabled = vendors.find(pair.first) != vendors.end(); + if (enabled) { + cbox->SetValue(true); - vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) { - this->on_vendor_pick(evt.GetInt()); - }); + auto pair = wizard_p()->pages_3rdparty.find(vendor->id); + wxCHECK_RET(pair != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); + pair->second->install = true; + } - append(vendor_picker); - for (PrinterPicker *picker : pickers) { this->append(picker); } -} - -void PageVendors::on_vendor_pick(size_t i) -{ - for (PrinterPicker *picker : pickers) { picker->Hide(); } - if (i < pickers.size()) { - pickers[i]->Show(); - parent->Layout(); + append(cbox); } } @@ -605,18 +927,18 @@ void PageDiameters::apply_custom_config(DynamicPrintConfig &config) auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { char buf[64]; sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); - config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); - }; + config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); + }; set_extrusion_width("support_material_extrusion_width", 0.35); - set_extrusion_width("top_infill_extrusion_width", 0.40); - set_extrusion_width("first_layer_extrusion_width", 0.42); + set_extrusion_width("top_infill_extrusion_width", 0.40); + set_extrusion_width("first_layer_extrusion_width", 0.42); - set_extrusion_width("extrusion_width", 0.45); - set_extrusion_width("perimeter_extrusion_width", 0.45); - set_extrusion_width("external_perimeter_extrusion_width", 0.45); - set_extrusion_width("infill_extrusion_width", 0.45); - set_extrusion_width("solid_infill_extrusion_width", 0.45); + set_extrusion_width("extrusion_width", 0.45); + set_extrusion_width("perimeter_extrusion_width", 0.45); + set_extrusion_width("external_perimeter_extrusion_width", 0.45); + set_extrusion_width("infill_extrusion_width", 0.45); + set_extrusion_width("solid_infill_extrusion_width", 0.45); } PageTemperatures::PageTemperatures(ConfigWizard *parent) @@ -684,8 +1006,8 @@ ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) , bullet_black(ScalableBitmap(parent, "bullet_black.png")) , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) , bullet_white(ScalableBitmap(parent, "bullet_white.png")) - , item_active(0) - , item_hover(-1) + , item_active(NO_ITEM) + , item_hover(NO_ITEM) , last_page((size_t)-1) { SetMinSize(bg.bmp().GetSize()); @@ -747,6 +1069,8 @@ void ConfigWizardIndex::go_prev() { // Search for a preceiding item that is a page (not a label, ie. page != nullptr) + if (item_active == NO_ITEM) { return; } + for (size_t i = item_active; i > 0; i--) { if (items[i - 1].page != nullptr) { go_to(i - 1); @@ -759,6 +1083,8 @@ void ConfigWizardIndex::go_next() { // Search for a next item that is a page (not a label, ie. page != nullptr) + if (item_active == NO_ITEM) { return; } + for (size_t i = item_active + 1; i < items.size(); i++) { if (items[i].page != nullptr) { go_to(i); @@ -767,28 +1093,39 @@ void ConfigWizardIndex::go_next() } } +// This one actually performs the go-to op void ConfigWizardIndex::go_to(size_t i) { - if (i < items.size() && items[i].page != nullptr) { + if (i != item_active + && i < items.size() + && items[i].page != nullptr) { + auto *new_active = items[i].page; auto *former_active = active_page(); - if (former_active != nullptr) { former_active->Hide(); } + if (former_active != nullptr) { + former_active->Hide(); + } item_active = i; - items[i].page->Show(); + new_active->Show(); wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); AddPendingEvent(evt); Refresh(); + + new_active->on_activate(); } } -void ConfigWizardIndex::go_to(ConfigWizardPage *page) +void ConfigWizardIndex::go_to(const ConfigWizardPage *page) { if (page == nullptr) { return; } for (size_t i = 0; i < items.size(); i++) { - if (items[i].page == page) { go_to(i); } + if (items[i].page == page) { + go_to(i); + return; + } } } @@ -798,7 +1135,7 @@ void ConfigWizardIndex::clear() if (former_active != nullptr) { former_active->Hide(); } items.clear(); - item_active = 0; + item_active = NO_ITEM; } void ConfigWizardIndex::on_paint(wxPaintEvent & evt) @@ -850,7 +1187,7 @@ void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) const ssize_t item_hover_new = pos.y / item_height(); - if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { + if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { item_hover = item_hover_new; Refresh(); } @@ -875,6 +1212,72 @@ void ConfigWizardIndex::msw_rescale() } +// Materials + +const std::string Materials::UNKNOWN = "(Unknown)"; + +void Materials::push(const Preset *preset) +{ + presets.insert(preset); + types.insert(technology & T_FFF + ? Materials::get_filament_type(preset) + : Materials::get_material_type(preset)); +} + +void Materials::clear() +{ + presets.clear(); + types.clear(); +} + +const std::string& Materials::appconfig_section() const +{ + return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; +} + +const std::string& Materials::get_type(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); +} + +const std::string& Materials::get_vendor(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); +} + +const std::string& Materials::get_filament_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_type"); + if (opt != nullptr && opt->values.size() > 0) { + return opt->values[0]; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_filament_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +const std::string& Materials::get_material_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_type"); + if (opt != nullptr) { + return opt->value; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_material_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + + // priv static const std::unordered_map> legacy_preset_map {{ @@ -888,25 +1291,40 @@ static const std::unordered_map { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, }}; -void ConfigWizard::priv::load_pages(bool custom_setup) +void ConfigWizard::priv::load_pages() { - const auto former_active = index->active_item(); + wxWindowUpdateLocker freeze_guard(q); + (void)freeze_guard; + + const ConfigWizardPage *former_active = index->active_page(); index->clear(); index->add_page(page_welcome); + + // Printers index->add_page(page_fff); index->add_page(page_msla); - index->add_page(page_custom); + index->add_page(page_vendors); + for (const auto &pair : pages_3rdparty) { + PagePrinters *page = pair.second; + if (page->install) { index->add_page(page); } + } - if (custom_setup) { + index->add_page(page_custom); + if (page_custom->custom_wanted()) { index->add_page(page_firmware); index->add_page(page_bed); index->add_page(page_diams); index->add_page(page_temps); } + // Filaments & Materials + if (any_fff_selected) { index->add_page(page_filaments); } + if (any_sla_selected) { index->add_page(page_sla_materials); } + index->add_page(page_update); + index->add_page(page_mode); index->go_to(former_active); // Will restore the active item/page if possible @@ -936,48 +1354,14 @@ void ConfigWizard::priv::init_dialog_size() q->SetSize(window_rect); } -bool ConfigWizard::priv::check_first_variant() const -{ - return run_reason == RR_DATA_EMPTY || run_reason == RR_DATA_LEGACY; -} - void ConfigWizard::priv::load_vendors() { - const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor"; - const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles"; - - // Load vendors from the "vendors" directory in datadir - for (auto &dir_entry : boost::filesystem::directory_iterator(vendor_dir)) - if (Slic3r::is_ini_file(dir_entry)) { - try { - auto vp = VendorProfile::from_ini(dir_entry.path()); - vendors[vp.id] = std::move(vp); - } - catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what(); - } - } - - // Additionally load up vendors from the application resources directory, but only those not seen in the datadir - for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_vendor_dir)) - if (Slic3r::is_ini_file(dir_entry)) { - const auto id = dir_entry.path().stem().string(); - if (vendors.find(id) == vendors.end()) { - try { - auto vp = VendorProfile::from_ini(dir_entry.path()); - vendors_rsrc[vp.id] = dir_entry.path().filename().string(); - vendors[vp.id] = std::move(vp); - } - catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what(); - } - } - } + bundles = BundleMap::load(); // Load up the set of vendors / models / variants the user has had enabled up till now - const AppConfig *app_config = GUI::get_app_config(); + AppConfig *app_config = wxGetApp().app_config; if (! app_config->legacy_datadir()) { - appconfig_vendors.set_vendors(*app_config); + appconfig_new.set_vendors(*app_config); } else { // In case of legacy datadir, try to guess the preference based on the printer preset files that are present const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; @@ -988,14 +1372,29 @@ void ConfigWizard::priv::load_vendors() const auto &model = needle->second.first; const auto &variant = needle->second.second; - appconfig_vendors.set_variant("PrusaResearch", model, variant, true); + appconfig_new.set_variant("PrusaResearch", model, variant, true); } } + + // Initialize the is_visible flag in printer Presets + for (auto &pair : bundles) { + pair.second.preset_bundle->load_installed_printers(appconfig_new); + } + + update_materials(T_ANY); + + if (app_config->has_section(AppConfig::SECTION_FILAMENTS)) { + appconfig_new.set_section(AppConfig::SECTION_FILAMENTS, app_config->get_section(AppConfig::SECTION_FILAMENTS)); + } + if (app_config->has_section(AppConfig::SECTION_MATERIALS)) { + appconfig_new.set_section(AppConfig::SECTION_MATERIALS, app_config->get_section(AppConfig::SECTION_MATERIALS)); + } } void ConfigWizard::priv::add_page(ConfigWizardPage *page) { hscroll_sizer->Add(page, 0, wxEXPAND); + all_pages.push_back(page); } void ConfigWizard::priv::enable_next(bool enable) @@ -1004,19 +1403,158 @@ void ConfigWizard::priv::enable_next(bool enable) btn_finish->Enable(enable); } -void ConfigWizard::priv::on_custom_setup(bool custom_wanted) +void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) { - load_pages(custom_wanted); + switch (start_page) { + case ConfigWizard::SP_PRINTERS: index->go_to(page_fff); break; + case ConfigWizard::SP_FILAMENTS: index->go_to(page_filaments); break; + case ConfigWizard::SP_MATERIALS: index->go_to(page_sla_materials); break; + default: index->go_to(page_welcome); break; + } +} + +void ConfigWizard::priv::create_3rdparty_pages() +{ + for (const auto &pair : bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + auto *page = new PagePrinters(q, vendor->name, vendor->name, *vendor, 1, T_ANY); + add_page(page); + + pages_3rdparty.insert({vendor->id, page}); + } +} + +void ConfigWizard::priv::set_run_reason(RunReason run_reason) +{ + this->run_reason = run_reason; + for (auto &page : all_pages) { + page->set_run_reason(run_reason); + } +} + +void ConfigWizard::priv::update_materials(Technology technology) +{ + if (any_fff_selected && (technology & T_FFF)) { + filaments.clear(); + + // Iterate filaments in all bundles + for (const auto &pair : bundles) { + for (const auto &filament : pair.second.preset_bundle->filaments) { + // Check if filament is already added + if (filaments.containts(&filament)) { continue; } + + // Iterate printers in all bundles + for (const auto &pair : bundles) { + for (const auto &printer : pair.second.preset_bundle->printers) { + // Filter out inapplicable printers + if (!printer.is_visible || printer.printer_technology() != ptFFF) { + continue; + } + + if (filament.is_compatible_with_printer(printer)) { + filaments.push(&filament); + } + } + } + } + } + } + + if (any_sla_selected && (technology & T_SLA)) { + sla_materials.clear(); + + // Iterate SLA materials in all bundles + for (const auto &pair : bundles) { + for (const auto &material : pair.second.preset_bundle->sla_materials) { + // Check if material is already added + if (sla_materials.containts(&material)) { continue; } + + // Iterate printers in all bundles + for (const auto &pair : bundles) { + for (const auto &printer : pair.second.preset_bundle->printers) { + // Filter out inapplicable printers + if (!printer.is_visible || printer.printer_technology() != ptSLA) { + continue; + } + + if (material.is_compatible_with_printer(printer)) { + sla_materials.push(&material); + } + } + } + } + } + } +} + +void ConfigWizard::priv::on_custom_setup() +{ + load_pages(); +} + +void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) +{ + if (page_msla->any_selected() != any_sla_selected || + page_fff->any_selected() != any_fff_selected) { + any_fff_selected = page_fff->any_selected(); + any_sla_selected = page_msla->any_selected(); + + load_pages(); + } + + // Update the is_visible flag on relevant printer profiles + for (auto &pair : bundles) { + if (pair.first != evt.vendor_id) { continue; } + + for (auto &preset : pair.second.preset_bundle->printers) { + if (preset.config.opt_string("printer_model") == evt.model_id + && preset.config.opt_string("printer_variant") == evt.variant_name) { + preset.is_visible = evt.enable; + } + } + } + + if (page == page_fff) { + page_filaments->clear(); + } else if (page == page_msla) { + page_sla_materials->clear(); + } +} + +void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) +{ + auto it = pages_3rdparty.find(vendor->id); + wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); + PagePrinters *page = it->second; + + if (page->install && !install) { + page->select_all(false); + } + page->install = install; + page->Layout(); + + load_pages(); } void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater) { - const auto enabled_vendors = appconfig_vendors.vendors(); + const auto enabled_vendors = appconfig_new.vendors(); // Install bundles from resources if needed: std::vector install_bundles; - for (const auto &vendor_rsrc : vendors_rsrc) { - const auto vendor = enabled_vendors.find(vendor_rsrc.first); + for (const auto &pair : bundles) { + if (! pair.second.is_in_resources) { continue; } + + if (pair.second.is_prusa_bundle) { + // Always install Prusa bundle, because it has a lot of filaments/materials + // likely to be referenced by other profiles. + install_bundles.emplace_back(pair.first); + continue; + } + + const auto vendor = enabled_vendors.find(pair.first); if (vendor == enabled_vendors.end()) { continue; } size_t size_sum = 0; @@ -1024,7 +1562,7 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (size_sum > 0) { // This vendor needs to be installed - install_bundles.emplace_back(vendor_rsrc.second); + install_bundles.emplace_back(pair.first); } } @@ -1066,20 +1604,27 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese preset_bundle->reset(true); } - app_config->set_vendors(appconfig_vendors); + app_config->set_vendors(appconfig_new); + if (appconfig_new.has_section(AppConfig::SECTION_FILAMENTS)) { + app_config->set_section(AppConfig::SECTION_FILAMENTS, appconfig_new.get_section(AppConfig::SECTION_FILAMENTS)); + } + if (appconfig_new.has_section(AppConfig::SECTION_MATERIALS)) { + app_config->set_section(AppConfig::SECTION_MATERIALS, appconfig_new.get_section(AppConfig::SECTION_MATERIALS)); + } app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + page_mode->serialize_mode(app_config); std::string preferred_model; - // Figure out the default pre-selected printer based on the seletions in the picker. + // Figure out the default pre-selected printer based on the selections in the pickers. // The default is the first selected printer model (one with at least 1 variant selected). // The default is only applied by load_presets() if the user doesn't have a (visible) printer // selected already. - const auto vendor_prusa = vendors.find("PrusaResearch"); + // Prusa printers are considered first, then 3rd party. const auto config_prusa = enabled_vendors.find("PrusaResearch"); - if (vendor_prusa != vendors.end() && config_prusa != enabled_vendors.end()) { - for (const auto &model : vendor_prusa->second.models) { + if (config_prusa != enabled_vendors.end()) { + for (const auto &model : bundles.prusa_bundle().vendor_profile->models) { const auto model_it = config_prusa->second.find(model.id); if (model_it != config_prusa->second.end() && model_it->second.size() > 0) { preferred_model = model.id; @@ -1087,6 +1632,20 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } } + if (preferred_model.empty()) { + for (const auto &bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + + const auto config = enabled_vendors.find(bundle.first); + for (const auto &model : bundle.second.vendor_profile->models) { + const auto model_it = config->second.find(model.id); + if (model_it != config->second.end() && model_it->second.size() > 0) { + preferred_model = model.id; + break; + } + } + } + } preset_bundle->load_presets(*app_config, preferred_model); @@ -1104,14 +1663,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese preset_bundle->export_selections(*app_config); } + // Public -ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) +ConfigWizard::ConfigWizard(wxWindow *parent) : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , p(new priv(this)) { this->SetFont(wxGetApp().normal_font()); - p->run_reason = reason; p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ @@ -1148,28 +1707,40 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); - const auto &vendors = p->vendors; - const auto vendor_prusa_it = vendors.find("PrusaResearch"); - wxCHECK_RET(vendor_prusa_it != vendors.cend(), "Vendor PrusaResearch not found"); - const VendorProfile &vendor_prusa = vendor_prusa_it->second; + const auto prusa_it = p->bundles.find("PrusaResearch"); + wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); + const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; p->add_page(p->page_welcome = new PageWelcome(this)); - p->page_fff = new PagePrinters(this, _(L("Prusa FFF Technology Printers")), "Prusa FFF", vendor_prusa, 0, PagePrinters::T_FFF); + p->page_fff = new PagePrinters(this, _(L("Prusa FFF Technology Printers")), "Prusa FFF", *vendor_prusa, 0, T_FFF); p->add_page(p->page_fff); - p->page_msla = new PagePrinters(this, _(L("Prusa MSLA Technology Printers")), "Prusa MSLA", vendor_prusa, 0, PagePrinters::T_SLA); + p->page_msla = new PagePrinters(this, _(L("Prusa MSLA Technology Printers")), "Prusa MSLA", *vendor_prusa, 0, T_SLA); p->add_page(p->page_msla); + p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, + _(L("Filament Profiles Selection")), _(L("Filaments")), _(L("Type:")) )); + p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, + _(L("SLA Material Profiles Selection")), _(L("SLA Materials")), _(L("Layer height:")) )); + p->add_page(p->page_custom = new PageCustom(this)); p->add_page(p->page_update = new PageUpdate(this)); - p->add_page(p->page_vendors = new PageVendors(this)); + p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); p->add_page(p->page_bed = new PageBedShape(this)); p->add_page(p->page_diams = new PageDiameters(this)); p->add_page(p->page_temps = new PageTemperatures(this)); - p->load_pages(false); + // Pages for 3rd party vendors + p->create_3rdparty_pages(); // Needs to ne done _before_ creating PageVendors + p->add_page(p->page_vendors = new PageVendors(this)); + + p->any_sla_selected = p->page_msla->any_selected(); + p->any_fff_selected = p->page_fff->any_selected(); + + p->load_pages(); + p->index->go_to(size_t{0}); vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); vsizer->Add(hline, 0, wxEXPAND); @@ -1191,9 +1762,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->btn_finish->Hide(); p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { + p->any_sla_selected = true; + p->load_pages(); p->page_fff->select_all(true, false); p->page_msla->select_all(true, false); - p->index->go_to(p->page_update); + p->index->go_to(p->page_mode); }); p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { @@ -1207,13 +1780,19 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) ConfigWizard::~ConfigWizard() {} -bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater) +bool ConfigWizard::run(RunReason reason, StartPage start_page) { - BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason; + BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; + + GUI_App &app = wxGetApp(); + + p->set_run_reason(reason); + p->set_start_page(start_page); + if (ShowModal() == wxID_OK) { - auto *app_config = GUI::get_app_config(); - p->apply_config(app_config, preset_bundle, updater); - app_config->set_legacy_datadir(false); + p->apply_config(app.app_config, app.preset_bundle, app.preset_updater); + app.app_config->set_legacy_datadir(false); + app.update_mode(); BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; return true; } else { @@ -1222,7 +1801,6 @@ bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater } } - const wxString& ConfigWizard::name(const bool from_menu/* = false*/) { // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. @@ -1243,7 +1821,7 @@ void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) { p->index->msw_rescale(); - const int& em = em_unit(); + const int em = em_unit(); msw_buttons_rescale(this, em, { wxID_APPLY, wxID_CANCEL, diff --git a/src/slic3r/GUI/ConfigWizard.hpp b/src/slic3r/GUI/ConfigWizard.hpp index b707e525ba..942f4b4ce8 100644 --- a/src/slic3r/GUI/ConfigWizard.hpp +++ b/src/slic3r/GUI/ConfigWizard.hpp @@ -26,7 +26,15 @@ public: RR_USER, // User requested the Wizard from the menus }; - ConfigWizard(wxWindow *parent, RunReason run_reason); + // What page should wizard start on + enum StartPage { + SP_WELCOME, + SP_PRINTERS, + SP_FILAMENTS, + SP_MATERIALS, + }; + + ConfigWizard(wxWindow *parent); ConfigWizard(ConfigWizard &&) = delete; ConfigWizard(const ConfigWizard &) = delete; ConfigWizard &operator=(ConfigWizard &&) = delete; @@ -34,7 +42,7 @@ public: ~ConfigWizard(); // Run the Wizard. Return whether it was completed. - bool run(PresetBundle *preset_bundle, const PresetUpdater *updater); + bool run(RunReason reason, StartPage start_page = SP_WELCOME); static const wxString& name(const bool from_menu = false); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index f4848933fc..995957816f 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -15,11 +15,14 @@ #include #include #include +#include +#include +#include #include "libslic3r/PrintConfig.hpp" #include "slic3r/Utils/PresetUpdater.hpp" #include "AppConfig.hpp" -#include "Preset.hpp" +#include "PresetBundle.hpp" #include "BedShapeDialog.hpp" namespace fs = boost::filesystem; @@ -41,6 +44,76 @@ enum { ROW_SPACING = 75, }; + + +// Configuration data structures extensions needed for the wizard + +enum Technology { + // Bitflag equivalent of PrinterTechnology + T_FFF = 0x1, + T_SLA = 0x2, + T_ANY = ~0, +}; + +struct Materials +{ + Technology technology; + std::set presets; + std::set types; + + Materials(Technology technology) : technology(technology) {} + + void push(const Preset *preset); + void clear(); + bool containts(const Preset *preset) { + return presets.find(preset) != presets.end(); + } + + const std::string& appconfig_section() const; + const std::string& get_type(const Preset *preset) const; + const std::string& get_vendor(const Preset *preset) const; + + template void filter_presets(const std::string &type, const std::string &vendor, F cb) { + for (const Preset *preset : presets) { + if ((type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor)) { + cb(preset); + } + } + } + + static const std::string UNKNOWN; + static const std::string& get_filament_type(const Preset *preset); + static const std::string& get_filament_vendor(const Preset *preset); + static const std::string& get_material_type(const Preset *preset); + static const std::string& get_material_vendor(const Preset *preset); +}; + +struct Bundle +{ + std::unique_ptr preset_bundle; + VendorProfile *vendor_profile; + const bool is_in_resources; + const bool is_prusa_bundle; + + Bundle(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); + Bundle(Bundle &&other); + + const std::string& vendor_id() const { return vendor_profile->id; } +}; + +struct BundleMap: std::unordered_map +{ + static BundleMap load(); + + Bundle& prusa_bundle(); + const Bundle& prusa_bundle() const; +}; + +struct PrinterPickerEvent; + + +// GUI elements + typedef std::function ModelFilter; struct PrinterPicker: wxPanel @@ -61,19 +134,22 @@ struct PrinterPicker: wxPanel std::vector cboxes; std::vector cboxes_alt; - PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors, const ModelFilter &filter); - PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors); + PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter); + PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig); void select_all(bool select, bool alternates = false); void select_one(size_t i, bool select); - void on_checkbox(const Checkbox *cbox, bool checked); + bool any_selected() const; int get_width() const { return width; } const std::vector& get_button_indexes() { return m_button_indexes; } + + static const std::string PRINTER_PLACEHOLDER; private: int width; - std::vector m_button_indexes; + + void on_checkbox(const Checkbox *cbox, bool checked); }; struct ConfigWizardPage: wxPanel @@ -87,43 +163,107 @@ struct ConfigWizardPage: wxPanel virtual ~ConfigWizardPage(); template - void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) + T* append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10) { content->Add(thing, proportion, flag, border); + return thing; } - void append_text(wxString text); + wxStaticText* append_text(wxString text); void append_spacer(int space); ConfigWizard::priv *wizard_p() const { return parent->p.get(); } virtual void apply_custom_config(DynamicPrintConfig &config) {} + virtual void set_run_reason(ConfigWizard::RunReason run_reason) {} + virtual void on_activate() {} }; struct PageWelcome: ConfigWizardPage { + wxStaticText *welcome_text; wxCheckBox *cbox_reset; PageWelcome(ConfigWizard *parent); bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; } + + virtual void set_run_reason(ConfigWizard::RunReason run_reason) override; }; struct PagePrinters: ConfigWizardPage { - enum Technology { - // Bitflag equivalent of PrinterTechnology - T_FFF = 0x1, - T_SLA = 0x2, - T_Any = ~0, - }; - std::vector printer_pickers; + Technology technology; + bool install; - PagePrinters(ConfigWizard *parent, wxString title, wxString shortname, const VendorProfile &vendor, unsigned indent, Technology technology); + PagePrinters(ConfigWizard *parent, + wxString title, + wxString shortname, + const VendorProfile &vendor, + unsigned indent, Technology technology); void select_all(bool select, bool alternates = false); int get_width() const; + bool any_selected() const; + + virtual void set_run_reason(ConfigWizard::RunReason run_reason) override; +}; + +// Here we extend wxListBox and wxCheckListBox +// to make the client data API much easier to use. +template struct DataList : public T +{ + DataList(wxWindow *parent) : T(parent, wxID_ANY) {} + + // Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing, + // eg. "ABS" is sorted before "(All)" + + int append(const std::string &label, const D *data) { + void *ptr = reinterpret_cast(const_cast(data)); + return this->Append(from_u8(label), ptr); + } + + int append(const wxString &label, const D *data) { + void *ptr = reinterpret_cast(const_cast(data)); + return this->Append(label, ptr); + } + + const D& get_data(int n) { + return *reinterpret_cast(this->GetClientData(n)); + } + + int find(const D &data) { + for (unsigned i = 0; i < this->GetCount(); i++) { + if (get_data(i) == data) { return i; } + } + + return wxNOT_FOUND; + } +}; + +typedef DataList StringList; +typedef DataList PresetList; + +struct PageMaterials: ConfigWizardPage +{ + Materials *materials; + StringList *list_l1, *list_l2; + PresetList *list_l3; + int sel1_prev, sel2_prev; + bool presets_loaded; + + static const std::string EMPTY; + + PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name); + + void reload_presets(); + void update_lists(int sel1, int sel2); + void select_material(int i); + void select_all(bool select); + void clear(); + + virtual void on_activate() override; }; struct PageCustom: ConfigWizardPage @@ -150,13 +290,22 @@ struct PageUpdate: ConfigWizardPage PageUpdate(ConfigWizard *parent); }; +struct PageMode: ConfigWizardPage +{ + wxRadioButton *radio_simple; + wxRadioButton *radio_advanced; + wxRadioButton *radio_expert; + + PageMode(ConfigWizard *parent); + + void serialize_mode(AppConfig *app_config) const; + + virtual void on_activate(); +}; + struct PageVendors: ConfigWizardPage { - std::vector pickers; - PageVendors(ConfigWizard *parent); - - void on_vendor_pick(size_t i); }; struct PageFirmware: ConfigWizardPage @@ -194,6 +343,8 @@ struct PageTemperatures: ConfigWizardPage virtual void apply_custom_config(DynamicPrintConfig &config); }; +typedef std::map Pages3rdparty; + class ConfigWizardIndex: public wxPanel { @@ -210,12 +361,14 @@ public: void go_prev(); void go_next(); void go_to(size_t i); - void go_to(ConfigWizardPage *page); + void go_to(const ConfigWizardPage *page); void clear(); void msw_rescale(); int em() const { return em_w; } + + static const size_t NO_ITEM = size_t(-1); private: struct Item { @@ -228,12 +381,6 @@ private: int em_w; int em_h; - /* #ys_FIXME_delete_after_testing by VK - const wxBitmap bg; - const wxBitmap bullet_black; - const wxBitmap bullet_blue; - const wxBitmap bullet_white; - */ ScalableBitmap bg; ScalableBitmap bullet_black; ScalableBitmap bullet_blue; @@ -245,9 +392,6 @@ private: ssize_t item_hover; size_t last_page; - /* #ys_FIXME_delete_after_testing by VK - int item_height() const { return std::max(bullet_black.GetSize().GetHeight(), em_w) + em_w; } - */ int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; } void on_paint(wxPaintEvent &evt); @@ -256,14 +400,24 @@ private: wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); + + +// ConfigWizard private data + struct ConfigWizard::priv { ConfigWizard *q; - ConfigWizard::RunReason run_reason; - AppConfig appconfig_vendors; - std::unordered_map vendors; - std::unordered_map vendors_rsrc; - std::unique_ptr custom_config; + ConfigWizard::RunReason run_reason = RR_USER; + AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI + BundleMap bundles; // Holds all loaded config bundles, the key is the vendor names. + // Materials refers to Presets in those bundles by pointers. + // Also we update the is_visible flag in printer Presets according to the + // PrinterPickers state. + Materials filaments; // Holds available filament presets and their types & vendors + Materials sla_materials; // Ditto for SLA materials + std::unique_ptr custom_config; // Backing for custom printer definition + bool any_fff_selected; // Used to decide whether to display Filaments page + bool any_sla_selected; // Used to decide whether to display SLA Materials page wxScrolledWindow *hscroll = nullptr; wxBoxSizer *hscroll_sizer = nullptr; @@ -279,9 +433,13 @@ struct ConfigWizard::priv PageWelcome *page_welcome = nullptr; PagePrinters *page_fff = nullptr; PagePrinters *page_msla = nullptr; + PageMaterials *page_filaments = nullptr; + PageMaterials *page_sla_materials = nullptr; PageCustom *page_custom = nullptr; PageUpdate *page_update = nullptr; - PageVendors *page_vendors = nullptr; // XXX: ? + PageMode *page_mode = nullptr; + PageVendors *page_vendors = nullptr; + Pages3rdparty pages_3rdparty; // Custom setup pages PageFirmware *page_firmware = nullptr; @@ -289,17 +447,30 @@ struct ConfigWizard::priv PageDiameters *page_diams = nullptr; PageTemperatures *page_temps = nullptr; - priv(ConfigWizard *q) : q(q) {} + // Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex) + std::vector all_pages; - void load_pages(bool custom_setup); + priv(ConfigWizard *q) + : q(q) + , filaments(T_FFF) + , sla_materials(T_SLA) + , any_sla_selected(false) + {} + + void load_pages(); void init_dialog_size(); - bool check_first_variant() const; void load_vendors(); void add_page(ConfigWizardPage *page); void enable_next(bool enable); + void set_start_page(ConfigWizard::StartPage start_page); + void create_3rdparty_pages(); + void set_run_reason(RunReason run_reason); + void update_materials(Technology technology); - void on_custom_setup(bool custom_wanted); + void on_custom_setup(); + void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt); + void on_3rdparty_install(const VendorProfile *vendor, bool install); void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 07d75c9472..42e3448fc1 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -150,7 +150,13 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true case coFloat:{ if (m_opt.type == coPercent && !str.IsEmpty() && str.Last() == '%') str.RemoveLast(); - else if (check_value && !str.IsEmpty() && str.Last() == '%') { + else if (!str.IsEmpty() && str.Last() == '%') + { + if (!check_value) { + m_value.clear(); + break; + } + wxString label = m_Label->GetLabel(); if (label.Last() == '\n') label.RemoveLast(); while (label.Last() == ' ') label.RemoveLast(); @@ -169,13 +175,21 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true { if (m_opt.nullable && str == na_value()) val = ConfigOptionFloatsNullable::nil_value(); - else if (check_value && !str.ToCDouble(&val)) + else if (!str.ToCDouble(&val)) { + if (!check_value) { + m_value.clear(); + break; + } show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); } - if (check_value && (m_opt.min > val || val > m_opt.max)) + if (m_opt.min > val || val > m_opt.max) { + if (!check_value) { + m_value.clear(); + break; + } show_error(m_parent, _(L("Input value is out of range"))); if (m_opt.min > val) val = m_opt.min; if (val > m_opt.max) val = m_opt.max; @@ -192,15 +206,24 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true double val = 0.; // Replace the first occurence of comma in decimal number. str.Replace(",", ".", false); - if (check_value && !str.ToCDouble(&val)) + if (!str.ToCDouble(&val)) { + if (!check_value) { + m_value.clear(); + break; + } show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); } - else if (check_value && ((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) || + else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) || (m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1)) && (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast(m_value))) { + if (!check_value) { + m_value.clear(); + break; + } + const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; const wxString stVal = double_to_string(val, 2); const wxString msg_text = wxString::Format(_(L("Do you mean %s%% instead of %s %s?\n" @@ -351,6 +374,7 @@ bool TextCtrl::value_was_changed() boost::any val = m_value; wxString ret_str = static_cast(window)->GetValue(); // update m_value! + // ret_str might be changed inside get_value_by_opt_type get_value_by_opt_type(ret_str); switch (m_opt.type) { @@ -396,8 +420,10 @@ void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) if (!change_event) { wxString ret_str = static_cast(window)->GetValue(); - // update m_value to correct work of next value_was_changed(), - // but don't check/change inputed value and don't show a warning message + /* Update m_value to correct work of next value_was_changed(). + * But after checking of entered value, don't fix the "incorrect" value and don't show a warning message, + * just clear m_value in this case. + */ get_value_by_opt_type(ret_str, false); } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5b1188169e..99f61e51ed 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7,6 +7,9 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "libslic3r/GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/Geometry.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Utils.hpp" @@ -1116,6 +1119,10 @@ wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); +#if ENABLE_THUMBNAIL_GENERATOR +const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; +#endif // ENABLE_THUMBNAIL_GENERATOR + GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) , m_context(nullptr) @@ -1643,6 +1650,18 @@ void GLCanvas3D::render() #endif // ENABLE_RENDER_STATISTICS } +#if ENABLE_THUMBNAIL_GENERATOR +void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background) +{ + switch (GLCanvas3DManager::get_framebuffers_type()) + { + case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; } + case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; } + default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; } + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR + void GLCanvas3D::select_all() { m_selection.add_all(); @@ -1747,101 +1766,114 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re _set_current(); struct ModelVolumeState { - ModelVolumeState(const GLVolume *volume) : - model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) : - model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} - ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) : - model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} - bool new_geometry() const { return this->volume_idx == size_t(-1); } - const ModelVolume *model_volume; + ModelVolumeState(const GLVolume* volume) : + model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} + ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) : + model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} + ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) : + model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} + bool new_geometry() const { return this->volume_idx == size_t(-1); } + const ModelVolume* model_volume; // ObjectID of ModelVolume + ObjectID of ModelInstance // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance std::pair geometry_id; GLVolume::CompositeID composite_id; // Volume index in the new GLVolume vector. - size_t volume_idx; + size_t volume_idx; }; std::vector model_volume_state; - std::vector aux_volume_state; + std::vector aux_volume_state; + + struct GLVolumeState { + GLVolumeState() : + volume_idx(-1) {} + GLVolumeState(const GLVolume* volume, unsigned int volume_idx) : + composite_id(volume->composite_id), volume_idx(volume_idx) {} + + GLVolume::CompositeID composite_id; + // Volume index in the old GLVolume vector. + size_t volume_idx; + }; // SLA steps to pull the preview meshes for. typedef std::array SLASteps; - SLASteps sla_steps = { slaposSupportTree, slaposBasePool }; + SLASteps sla_steps = { slaposSupportTree, slaposPad }; struct SLASupportState { - std::array::value> step; + std::array::value> step; }; // State of the sla_steps for all SLAPrintObjects. std::vector sla_support_state; std::vector instance_ids_selected; std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); + std::vector deleted_volumes; std::vector glvolumes_new; glvolumes_new.reserve(m_volumes.volumes.size()); - auto model_volume_state_lower = [](const ModelVolumeState &m1, const ModelVolumeState &m2) { return m1.geometry_id < m2.geometry_id; }; + auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; }; - m_reload_delayed = ! m_canvas->IsShown() && ! refresh_immediately && ! force_full_scene_refresh; + m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; - PrinterTechnology printer_technology = m_process->current_printer_technology(); + PrinterTechnology printer_technology = m_process->current_printer_technology(); int volume_idx_wipe_tower_old = -1; // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). // First initialize model_volumes_new_sorted & model_instances_new_sorted. - for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) { - const ModelObject *model_object = m_model->objects[object_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) { - const ModelInstance *model_instance = model_object->instances[instance_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); + for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) { + const ModelObject* model_object = m_model->objects[object_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) { + const ModelInstance* model_instance = model_object->instances[instance_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) { + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); } } } if (printer_technology == ptSLA) { - const SLAPrint *sla_print = this->sla_print(); - #ifndef NDEBUG + const SLAPrint* sla_print = this->sla_print(); +#ifndef NDEBUG // Verify that the SLAPrint object is synchronized with m_model. check_model_ids_equal(*m_model, sla_print->model()); - #endif /* NDEBUG */ +#endif /* NDEBUG */ sla_support_state.reserve(sla_print->objects().size()); - for (const SLAPrintObject *print_object : sla_print->objects()) { + for (const SLAPrintObject* print_object : sla_print->objects()) { SLASupportState state; - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) { - state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { - if (! print_object->has_mesh(sla_steps[istep])) + for (size_t istep = 0; istep < sla_steps.size(); ++istep) { + state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); + if (state.step[istep].state == PrintStateBase::DONE) { + if (!print_object->has_mesh(sla_steps[istep])) // Consider the DONE step without a valid mesh as invalid for the purpose // of mesh visualization. state.step[istep].state = PrintStateBase::INVALID; else - for (const ModelInstance *model_instance : print_object->model_object()->instances) - // Only the instances, which are currently printable, will have the SLA support structures kept. - // The instances outside the print bed will have the GLVolumes of their support structures released. - if (model_instance->is_printable()) + for (const ModelInstance* model_instance : print_object->model_object()->instances) + // Only the instances, which are currently printable, will have the SLA support structures kept. + // The instances outside the print bed will have the GLVolumes of their support structures released. + if (model_instance->is_printable()) aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); } - } - sla_support_state.emplace_back(state); + } + sla_support_state.emplace_back(state); } } std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); - std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower); + std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower); // Release all ModelVolume based GLVolumes not found in the current Model. - for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) { - GLVolume *volume = m_volumes.volumes[volume_id]; + for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) { + GLVolume* volume = m_volumes.volumes[volume_id]; ModelVolumeState key(volume); - ModelVolumeState *mvs = nullptr; + ModelVolumeState* mvs = nullptr; if (volume->volume_idx() < 0) { - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) // This can be an SLA support structure that should not be rendered (in case someone used undo // to revert to before it was generated). We only reuse the volume if that's not the case. if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) mvs = &(*it); - } else { - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + } + else { + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); + mvs = &(*it); } // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. // The wipe tower has its own wipe_tower_instance_id(). @@ -1854,19 +1886,23 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } - if (! m_reload_delayed) + if (!m_reload_delayed) + { + deleted_volumes.emplace_back(volume, volume_id); delete volume; - } else { + } + } + else { // This GLVolume will be reused. volume->set_sla_shift_z(0.0); map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); mvs->volume_idx = glvolumes_new.size(); glvolumes_new.emplace_back(volume); // Update color of the volume based on the current extruder. - if (mvs->model_volume != nullptr) { - int extruder_id = mvs->model_volume->extruder_id(); - if (extruder_id != -1) - volume->extruder_id = extruder_id; + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; volume->is_modifier = !mvs->model_volume->is_model_part(); volume->set_color_from_model_volume(mvs->model_volume); @@ -1884,6 +1920,16 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re bool update_object_list = false; + auto find_old_volume_id = [&deleted_volumes](const GLVolume::CompositeID& id) -> unsigned int { + for (unsigned int i = 0; i < (unsigned int)deleted_volumes.size(); ++i) + { + const GLVolumeState& v = deleted_volumes[i]; + if (v.composite_id == id) + return v.volume_idx; + } + return (unsigned int)-1; + }; + if (m_volumes.volumes != glvolumes_new) update_object_list = true; m_volumes.volumes = std::move(glvolumes_new); @@ -1898,9 +1944,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) { // New volume. + unsigned int old_id = find_old_volume_id(it->composite_id); + if (old_id != (unsigned int)-1) + map_glvolume_old_to_new[old_id] = m_volumes.volumes.size(); m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized); m_volumes.volumes.back()->geometry_id = key.geometry_id; - update_object_list = true; + update_object_list = true; } else { // Recycling an old GLVolume. GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; @@ -1999,19 +2048,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; const Print *print = m_process->fff_print(); - float depth = print->get_wipe_tower_depth(); - // Calculate wipe tower brim spacing. const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; double layer_height = print_config.opt_float("layer_height"); double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); - float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + double nozzle_diameter = print->config().nozzle_diameter.values[0]; + float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth; + float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width; - if (!print->is_step_done(psWipeTower)) - depth = (900.f/w) * (float)(extruders_count - 1); int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_spacing * 4.5f, m_initialized); + brim_width, m_initialized); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -2634,19 +2681,6 @@ std::string format_mouse_event_debug_message(const wxMouseEvent &evt) void GLCanvas3D::on_mouse(wxMouseEvent& evt) { - auto mouse_up_cleanup = [this](){ - m_moving = false; - m_mouse.drag.move_volume_idx = -1; - m_mouse.set_start_position_3D_as_invalid(); - m_mouse.set_start_position_2D_as_invalid(); - m_mouse.dragging = false; - m_mouse.ignore_left_up = false; - m_dirty = true; - - if (m_canvas->HasCapture()) - m_canvas->ReleaseMouse(); - }; - #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor(); evt.SetX(evt.GetX() * scale); @@ -3483,6 +3517,20 @@ void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const m_volumes.export_toolpaths_to_obj(filename); } +void GLCanvas3D::mouse_up_cleanup() +{ + m_moving = false; + m_mouse.drag.move_volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.set_start_position_2D_as_invalid(); + m_mouse.dragging = false; + m_mouse.ignore_left_up = false; + m_dirty = true; + + if (m_canvas->HasCapture()) + m_canvas->ReleaseMouse(); +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -3524,6 +3572,341 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) imgui->end(); } +#if ENABLE_THUMBNAIL_GENERATOR +#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT +static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) +{ + // debug export of generated image + wxImage image(thumbnail_data.width, thumbnail_data.height); + image.InitAlpha(); + + for (unsigned int r = 0; r < thumbnail_data.height; ++r) + { + unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width; + for (unsigned int c = 0; c < thumbnail_data.width; ++c) + { + unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c); + image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); + image.SetAlpha((int)c, (int)r, px[3]); + } + } + + image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG); +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + + +static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volumes, ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool transparent_background) +{ + auto is_visible = [](const GLVolume& v) -> bool + { + bool ret = v.printable; + ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside); + return ret; + }; + + static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f }; + static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f }; + + GLVolumePtrs visible_volumes; + + for (GLVolume* vol : volumes) + { + if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0))) + { + if (!printable_only || is_visible(*vol)) + visible_volumes.push_back(vol); + } + } + + if (visible_volumes.empty()) + return; + + BoundingBoxf3 box; + for (const GLVolume* vol : visible_volumes) + { + box.merge(vol->transformed_bounding_box()); + } + + Camera camera; + camera.set_type(Camera::Ortho); + camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height); + camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); + camera.apply_view_matrix(); + camera.apply_projection(box); + + if (transparent_background) + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 0.0f)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader.start_using(); + + GLint shader_id = shader.get_shader_program_id(); + GLint color_id = ::glGetUniformLocation(shader_id, "uniform_color"); + GLint print_box_detection_id = ::glGetUniformLocation(shader_id, "print_box.volume_detection"); + glcheck(); + + if (print_box_detection_id != -1) + glsafe(::glUniform1i(print_box_detection_id, 0)); + + for (const GLVolume* vol : visible_volumes) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray)); + else + glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray)); + + vol->render(); + } + + shader.stop_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + if (transparent_background) + glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffers(1, &render_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) + { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffers(1, &render_tex_buffer)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer)); + } + else + { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffers(1, &render_depth)); + glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + { + render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background); + + if (multisample) + { + GLuint resolve_fbo; + glsafe(::glGenFramebuffers(1, &resolve_fbo)); + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + { + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo)); + glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo)); + glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffers(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + } + + glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0)); + glsafe(::glDeleteRenderbuffers(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffers(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background) +{ + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + bool multisample = m_multisample_allowed; + if (multisample) + glsafe(::glEnable(GL_MULTISAMPLE)); + + GLint max_samples; + glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples)); + GLsizei num_samples = max_samples / 2; + + GLuint render_fbo; + glsafe(::glGenFramebuffersEXT(1, &render_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo)); + + GLuint render_tex = 0; + GLuint render_tex_buffer = 0; + if (multisample) + { + // use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2 + glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer)); + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h)); + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer)); + } + else + { + glsafe(::glGenTextures(1, &render_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0)); + } + + GLuint render_depth; + glsafe(::glGenRenderbuffersEXT(1, &render_depth)); + glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth)); + if (multisample) + glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h)); + else + glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)); + + glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth)); + + GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 }; + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) + { + render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background); + + if (multisample) + { + GLuint resolve_fbo; + glsafe(::glGenFramebuffersEXT(1, &resolve_fbo)); + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo)); + + GLuint resolve_tex; + glsafe(::glGenTextures(1, &resolve_tex)); + glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0)); + + glsafe(::glDrawBuffers(1, drawBufs)); + + if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) + { + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo)); + glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR)); + + glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo)); + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + } + + glsafe(::glDeleteTextures(1, &resolve_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo)); + } + else + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + } + + glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)); + glsafe(::glDeleteRenderbuffersEXT(1, &render_depth)); + if (render_tex_buffer != 0) + glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer)); + if (render_tex != 0) + glsafe(::glDeleteTextures(1, &render_tex)); + glsafe(::glDeleteFramebuffersEXT(1, &render_fbo)); + + if (multisample) + glsafe(::glDisable(GL_MULTISAMPLE)); +} + +void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background) +{ + // check that thumbnail size does not exceed the default framebuffer size + const Size& cnv_size = get_canvas_size(); + unsigned int cnv_w = (unsigned int)cnv_size.get_width(); + unsigned int cnv_h = (unsigned int)cnv_size.get_height(); + if ((w > cnv_w) || (h > cnv_h)) + { + float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h); + w = (unsigned int)(ratio * (float)w); + h = (unsigned int)(ratio * (float)h); + } + + thumbnail_data.set(w, h); + if (!thumbnail_data.is_valid()) + return; + + render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background); + + glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data())); +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + debug_output_thumbnail(thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT + + // restore the default framebuffer size to avoid flickering on the 3D scene + m_camera.apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height()); +} +#endif // ENABLE_THUMBNAIL_GENERATOR + bool GLCanvas3D::_init_toolbars() { if (!_init_main_toolbar()) @@ -3835,12 +4218,21 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be return bb; } +#if ENABLE_THUMBNAIL_GENERATOR +void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) +{ + const Size& cnv_size = get_canvas_size(); + m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor); + m_dirty = true; +} +#else void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box) { const Size& cnv_size = get_canvas_size(); m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height()); m_dirty = true; } +#endif // ENABLE_THUMBNAIL_GENERATOR void GLCanvas3D::_refresh_if_shown_on_screen() { @@ -4037,7 +4429,9 @@ void GLCanvas3D::_render_objects() const if (m_volumes.empty()) return; +#if !ENABLE_THUMBNAIL_GENERATOR glsafe(::glEnable(GL_LIGHTING)); +#endif // !ENABLE_THUMBNAIL_GENERATOR glsafe(::glEnable(GL_DEPTH_TEST)); m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); @@ -4081,7 +4475,9 @@ void GLCanvas3D::_render_objects() const m_shader.stop_using(); m_camera_clipping_plane = ClippingPlane::ClipsNothing(); +#if !ENABLE_THUMBNAIL_GENERATOR glsafe(::glDisable(GL_LIGHTING)); +#endif // !ENABLE_THUMBNAIL_GENERATOR } void GLCanvas3D::_render_selection() const @@ -4987,19 +5383,21 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat // helper functions to select data in dependence of the extrusion view type struct Helper { - static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path) + static float path_filter(GCodePreviewData::Extrusion::EViewType type, const GCodePreviewData::Extrusion::Path& path) { switch (type) { case GCodePreviewData::Extrusion::FeatureType: // The role here is used for coloring. - return (float)path.role(); + return (float)path.extrusion_role; case GCodePreviewData::Extrusion::Height: return path.height; case GCodePreviewData::Extrusion::Width: return path.width; case GCodePreviewData::Extrusion::Feedrate: return path.feedrate; + case GCodePreviewData::Extrusion::FanSpeed: + return path.fan_speed; case GCodePreviewData::Extrusion::VolumetricRate: return path.feedrate * (float)path.mm3_per_mm; case GCodePreviewData::Extrusion::Tool: @@ -5025,6 +5423,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat return data.get_width_color(value); case GCodePreviewData::Extrusion::Feedrate: return data.get_feedrate_color(value); + case GCodePreviewData::Extrusion::FanSpeed: + return data.get_fan_speed_color(value); case GCodePreviewData::Extrusion::VolumetricRate: return data.get_volumetric_rate_color(value); case GCodePreviewData::Extrusion::Tool: @@ -5067,15 +5467,15 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat { std::vector num_paths_per_role(size_t(erCount), 0); for (const GCodePreviewData::Extrusion::Layer &layer : preview_data.extrusion.layers) - for (const ExtrusionPath &path : layer.paths) - ++ num_paths_per_role[size_t(path.role())]; + for (const GCodePreviewData::Extrusion::Path &path : layer.paths) + ++ num_paths_per_role[size_t(path.extrusion_role)]; std::vector> roles_values; roles_values.assign(size_t(erCount), std::vector()); for (size_t i = 0; i < roles_values.size(); ++ i) roles_values[i].reserve(num_paths_per_role[i]); for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) - for (const ExtrusionPath& path : layer.paths) - roles_values[size_t(path.role())].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path)); + for (const GCodePreviewData::Extrusion::Path &path : layer.paths) + roles_values[size_t(path.extrusion_role)].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path)); roles_filters.reserve(size_t(erCount)); size_t num_buffers = 0; for (std::vector &values : roles_values) { @@ -5103,9 +5503,9 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat // populates volumes for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) { - for (const ExtrusionPath& path : layer.paths) + for (const GCodePreviewData::Extrusion::Path& path : layer.paths) { - std::vector> &filters = roles_filters[size_t(path.role())]; + std::vector> &filters = roles_filters[size_t(path.extrusion_role)]; auto key = std::make_pair(Helper::path_filter(preview_data.extrusion.view_type, path), nullptr); auto it_filter = std::lower_bound(filters.begin(), filters.end(), key); assert(it_filter != filters.end() && key.first == it_filter->first); @@ -5115,7 +5515,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - _3DScene::extrusionentity_to_verts(path, layer.z, vol); + _3DScene::extrusionentity_to_verts(path.polyline, path.width, path.height, layer.z, vol); } // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. for (std::vector> &filters : roles_filters) { @@ -5280,20 +5680,18 @@ void GLCanvas3D::_load_fff_shells() // adds wipe tower's volume double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print->config(); - unsigned int extruders_count = config.nozzle_diameter.size(); + size_t extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { - float depth = print->get_wipe_tower_depth(); - // Calculate wipe tower brim spacing. const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; double layer_height = print_config.opt_float("layer_height"); double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); - float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + double nozzle_diameter = print->config().nozzle_diameter.values[0]; + float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth; + float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width; - if (!print->is_step_done(psWipeTower)) - depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1); m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print->is_step_done(psWipeTower), brim_spacing * 4.5f, m_initialized); + !print->is_step_done(psWipeTower), brim_width, m_initialized); } } } @@ -5336,8 +5734,8 @@ void GLCanvas3D::_load_sla_shells() m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); - if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool)) - add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); + if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) + add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); } double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index fb767360c3..8c2e6e9a58 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -36,6 +36,9 @@ class GLShader; class ExPolygon; class BackgroundSlicingProcess; class GCodePreviewData; +#if ENABLE_THUMBNAIL_GENERATOR +struct ThumbnailData; +#endif // ENABLE_THUMBNAIL_GENERATOR struct SlicingParameters; enum LayerHeightEditActionType : unsigned int; @@ -104,6 +107,10 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); class GLCanvas3D { +#if ENABLE_THUMBNAIL_GENERATOR + static const double DefaultCameraZoomToBoxMarginFactor; +#endif // ENABLE_THUMBNAIL_GENERATOR + public: struct GCodePreviewVolumeIndex { @@ -519,6 +526,11 @@ public: bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } void render(); +#if ENABLE_THUMBNAIL_GENERATOR + // printable_only == false -> render also non printable volumes as grayed + // parts_only == false -> render also sla support and pad + void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background); +#endif // ENABLE_THUMBNAIL_GENERATOR void select_all(); void deselect_all(); @@ -624,6 +636,8 @@ public: bool has_toolpaths_to_export() const; void export_toolpaths_to_obj(const char* filename) const; + void mouse_up_cleanup(); + private: bool _is_shown_on_screen() const; @@ -636,7 +650,11 @@ private: BoundingBoxf3 _max_bounding_box(bool include_gizmos, bool include_bed_model) const; +#if ENABLE_THUMBNAIL_GENERATOR + void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor); +#else void _zoom_to_box(const BoundingBoxf3& box); +#endif // ENABLE_THUMBNAIL_GENERATOR void _refresh_if_shown_on_screen(); @@ -664,6 +682,14 @@ private: void _render_sla_slices() const; void _render_selection_sidebar_hints() const; void _render_undo_redo_stack(const bool is_undo, float pos_x); +#if ENABLE_THUMBNAIL_GENERATOR + // render thumbnail using an off-screen framebuffer + void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background); + // render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported + void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background); + // render thumbnail using the default framebuffer + void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background); +#endif // ENABLE_THUMBNAIL_GENERATOR void _update_volumes_hover_state() const; diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 5fbefcc6e6..3594e85a42 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -107,7 +107,9 @@ void GLCanvas3DManager::GLInfo::detect() const m_renderer = data; glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); - + + m_max_tex_size /= 2; + if (GLEW_EXT_texture_filter_anisotropic) glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy)); @@ -187,6 +189,7 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown; bool GLCanvas3DManager::s_compressed_textures_supported = false; +GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::FB_None; GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() @@ -267,6 +270,13 @@ void GLCanvas3DManager::init_gl() else s_compressed_textures_supported = false; + if (GLEW_ARB_framebuffer_object) + s_framebuffers_type = FB_Arb; + else if (GLEW_EXT_framebuffer_object) + s_framebuffers_type = FB_Ext; + else + s_framebuffers_type = FB_None; + if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) { // Complain about the OpenGL version. wxString message = wxString::Format( diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 760266a274..940e0230ae 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -30,6 +30,13 @@ struct Camera; class GLCanvas3DManager { public: + enum EFramebufferType : unsigned char + { + FB_None, + FB_Arb, + FB_Ext + }; + class GLInfo { mutable bool m_detected; @@ -77,6 +84,7 @@ private: bool m_gl_initialized; static EMultisampleState s_multisample; static bool s_compressed_textures_supported; + static EFramebufferType s_framebuffers_type; public: GLCanvas3DManager(); @@ -97,6 +105,8 @@ public: static bool can_multisample() { return s_multisample == MS_Enabled; } static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } + static bool are_framebuffers_supported() { return (s_framebuffers_type != FB_None); } + static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } static wxGLCanvas* create_wxglcanvas(wxWindow *parent); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 55ca5f7238..62129cfc08 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -107,8 +107,8 @@ void GLTexture::Compressor::compress() break; // stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4, - // crashes if doing so, so we start with twice the required size - level.compressed_data = std::vector(level.w * level.h * 2, 0); + // crashes if doing so, requiring a minimum of 16 bytes and up to a third of the source buffer size, so we set the destination buffer initial size to be half the source buffer size + level.compressed_data = std::vector(std::max((unsigned int)16, level.w * level.h * 2), 0); int compressed_size = 0; rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size); level.compressed_data.resize(compressed_size); @@ -455,8 +455,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo int lod_w = m_width; int lod_h = m_height; GLint level = 0; - // we do not need to generate all levels down to 1x1 - while ((lod_w > 16) || (lod_h > 16)) + while ((lod_w > 1) || (lod_h > 1)) { ++level; @@ -600,8 +599,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo int lod_w = m_width; int lod_h = m_height; GLint level = 0; - // we do not need to generate all levels down to 1x1 - while ((lod_w > 16) || (lod_h > 16)) + while ((lod_w > 1) || (lod_h > 1)) { ++level; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 6e8c361c82..c22fd6f792 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -101,49 +101,6 @@ const std::string& shortkey_alt_prefix() return str; } -bool config_wizard_startup(bool app_config_exists) -{ - if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) { - config_wizard(ConfigWizard::RR_DATA_EMPTY); - return true; - } else if (get_app_config()->legacy_datadir()) { - // Looks like user has legacy pre-vendorbundle data directory, - // explain what this is and run the wizard - - MsgDataLegacy dlg; - dlg.ShowModal(); - - config_wizard(ConfigWizard::RR_DATA_LEGACY); - return true; - } - return false; -} - -void config_wizard(int reason) -{ - // Exit wizard if there are unsaved changes and the user cancels the action. - if (! wxGetApp().check_unsaved_changes()) - return; - - try { - ConfigWizard wizard(nullptr, static_cast(reason)); - wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater); - } - catch (const std::exception &e) { - show_error(nullptr, e.what()); - } - - wxGetApp().load_current_presets(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model())) - { - show_info(nullptr, - _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + - _(L("Please check and fix your object list.")), - _(L("Attention!"))); - } -} - // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index 4074c2afc8..0b904bad86 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -35,14 +35,6 @@ extern AppConfig* get_app_config(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); -// Checks if configuration wizard needs to run, calls config_wizard if so. -// Returns whether the Wizard ran. -extern bool config_wizard_startup(bool app_config_exists); - -// Opens the configuration wizard, returns true if wizard is finished & accepted. -// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl. -extern void config_wizard(int run_reason); - // Change option value in config void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 86523cb88a..0b24e22157 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -38,7 +38,6 @@ #include "../Utils/PresetUpdater.hpp" #include "../Utils/PrintHost.hpp" #include "../Utils/MacDarkMode.hpp" -#include "ConfigWizard.hpp" #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" #include "FirmwareDialog.hpp" @@ -46,11 +45,17 @@ #include "Tab.hpp" #include "SysInfoDialog.hpp" #include "KBShortcutsDialog.hpp" +#include "UpdateDialogs.hpp" #ifdef __WXMSW__ #include #endif // __WXMSW__ +#if ENABLE_THUMBNAIL_GENERATOR +#include +#include +#endif // ENABLE_THUMBNAIL_GENERATOR + namespace Slic3r { namespace GUI { @@ -148,6 +153,7 @@ GUI_App::GUI_App() : wxApp() , m_em_unit(10) , m_imgui(new ImGuiWrapper()) + , m_wizard(nullptr) {} GUI_App::~GUI_App() @@ -204,7 +210,6 @@ bool GUI_App::on_init_inner() // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); - app_conf_exists = app_config->exists(); // load settings app_conf_exists = app_config->exists(); if (app_conf_exists) { @@ -287,7 +292,7 @@ bool GUI_App::on_init_inner() } CallAfter([this] { - config_wizard_startup(app_conf_exists); + config_wizard_startup(); preset_updater->slic3r_update_notify(); preset_updater->sync(preset_bundle); }); @@ -826,7 +831,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { switch (event.GetId() - config_id_base) { case ConfigMenuWizard: - config_wizard(ConfigWizard::RR_USER); + run_wizard(ConfigWizard::RR_USER); break; case ConfigMenuTakeSnapshot: // Take a configuration snapshot. @@ -1057,6 +1062,142 @@ void GUI_App::open_web_page_localized(const std::string &http_address) wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe()); } +bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) +{ + wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + + if (! m_wizard) { + m_wizard = new ConfigWizard(mainframe); + } + + const bool res = m_wizard->run(reason, start_page); + + if (res) { + load_current_presets(); + + if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA + && Slic3r::model_has_multi_part_objects(wxGetApp().model())) { + GUI::show_info(nullptr, + _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + + _(L("Please check and fix your object list.")), + _(L("Attention!"))); + } + } + + return res; +} + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +void GUI_App::gcode_thumbnails_debug() +{ + const std::string BEGIN_MASK = "; thumbnail begin"; + const std::string END_MASK = "; thumbnail end"; + std::string gcode_line; + bool reading_image = false; + unsigned int width = 0; + unsigned int height = 0; + + wxFileDialog dialog(GetTopWindow(), _(L("Select a gcode file:")), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; + + std::string in_filename = into_u8(dialog.GetPath()); + std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); + + boost::nowide::ifstream in_file(in_filename.c_str()); + std::vector rows; + std::string row; + if (in_file.good()) + { + while (std::getline(in_file, gcode_line)) + { + if (in_file.good()) + { + if (boost::starts_with(gcode_line, BEGIN_MASK)) + { + reading_image = true; + gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); + std::string::size_type x_pos = gcode_line.find('x'); + std::string width_str = gcode_line.substr(0, x_pos); + width = (unsigned int)::atoi(width_str.c_str()); + std::string height_str = gcode_line.substr(x_pos + 1); + height = (unsigned int)::atoi(height_str.c_str()); + row.clear(); + } + else if (reading_image && boost::starts_with(gcode_line, END_MASK)) + { +#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; + boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); + if (out_file.good()) + { + std::string decoded = boost::beast::detail::base64_decode(row); + out_file.write(decoded.c_str(), decoded.length()); + out_file.close(); + } +#else + if (!row.empty()) + { + rows.push_back(row); + row.clear(); + } + + if ((unsigned int)rows.size() == height) + { + std::vector thumbnail(4 * width * height, 0); + for (unsigned int r = 0; r < (unsigned int)rows.size(); ++r) + { + std::string decoded_row = boost::beast::detail::base64_decode(rows[r]); + if ((unsigned int)decoded_row.length() == width * 4) + { + void* image_ptr = (void*)(thumbnail.data() + r * width * 4); + ::memcpy(image_ptr, (const void*)decoded_row.c_str(), width * 4); + } + } + + wxImage image(width, height); + image.InitAlpha(); + + for (unsigned int r = 0; r < height; ++r) + { + unsigned int rr = r * width; + for (unsigned int c = 0; c < width; ++c) + { + unsigned char* px = thumbnail.data() + 4 * (rr + c); + image.SetRGB((int)c, (int)r, px[0], px[1], px[2]); + image.SetAlpha((int)c, (int)r, px[3]); + } + } + + image.SaveFile(out_path + std::to_string(width) + "x" + std::to_string(height) + ".png", wxBITMAP_TYPE_PNG); + } +#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + + reading_image = false; + width = 0; + height = 0; + rows.clear(); + } + else if (reading_image) + { +#if !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + if (!row.empty() && (gcode_line[1] == ' ')) + { + rows.push_back(row); + row.clear(); + } +#endif // !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE + + row += gcode_line.substr(2); + } + } + } + + in_file.close(); + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) { if (name.empty()) { return; } @@ -1105,6 +1246,24 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) } } +bool GUI_App::config_wizard_startup() +{ + if (!app_conf_exists || preset_bundle->printers.size() <= 1) { + run_wizard(ConfigWizard::RR_DATA_EMPTY); + return true; + } else if (get_app_config()->legacy_datadir()) { + // Looks like user has legacy pre-vendorbundle data directory, + // explain what this is and run the wizard + + MsgDataLegacy dlg; + dlg.ShowModal(); + + run_wizard(ConfigWizard::RR_DATA_LEGACY); + return true; + } + return false; +} + // static method accepting a wxWindow object as first parameter // void warning_catcher{ // my($self, $message_dialog) = @_; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a8043e9915..bc912086e8 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -6,6 +6,7 @@ #include "libslic3r/PrintConfig.hpp" #include "MainFrame.hpp" #include "ImGuiWrapper.hpp" +#include "ConfigWizard.hpp" #include #include @@ -69,6 +70,7 @@ enum ConfigMenuIDs { }; class Tab; +class ConfigWizard; static wxString dots("…", wxConvUTF8); @@ -85,7 +87,7 @@ class GUI_App : public wxApp wxFont m_bold_font; wxFont m_normal_font; - size_t m_em_unit; // width of a "m"-symbol in pixels for current system font + int m_em_unit; // width of a "m"-symbol in pixels for current system font // Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls std::unique_ptr m_wxLocale; @@ -96,13 +98,14 @@ class GUI_App : public wxApp std::unique_ptr m_imgui; std::unique_ptr m_printhost_job_queue; + ConfigWizard* m_wizard; // Managed by wxWindow tree public: bool OnInit() override; bool initialized() const { return m_initialized; } GUI_App(); - ~GUI_App(); + ~GUI_App() override; static unsigned get_colour_approx_luma(const wxColour &colour); static bool dark_mode(); @@ -121,8 +124,7 @@ public: const wxFont& small_font() { return m_small_font; } const wxFont& bold_font() { return m_bold_font; } const wxFont& normal_font() { return m_normal_font; } - size_t em_unit() const { return m_em_unit; } - void set_em_unit(const size_t em_unit) { m_em_unit = em_unit; } + int em_unit() const { return m_em_unit; } float toolbar_icon_scale(const bool is_limited = false) const; void recreate_GUI(); @@ -152,7 +154,7 @@ public: // Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US". wxString current_language_code_safe() const; - virtual bool OnExceptionInMainLoop(); + virtual bool OnExceptionInMainLoop() override; #ifdef __APPLE__ // wxWidgets override to get an event on open files. @@ -184,6 +186,12 @@ public: PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } void open_web_page_localized(const std::string &http_address); + bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); + +#if ENABLE_THUMBNAIL_GENERATOR + // temporary and debug only -> extract thumbnails from selected gcode and save them as png files + void gcode_thumbnails_debug(); +#endif // ENABLE_THUMBNAIL_GENERATOR private: bool on_init_inner(); @@ -191,6 +199,9 @@ private: void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false); void window_pos_sanitize(wxTopLevelWindow* window); bool select_language(); + + bool config_wizard_startup(); + #ifdef __WXMSW__ void associate_3mf_files(); #endif // __WXMSW__ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 19dedc7b0e..a88980a8d4 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -131,7 +131,7 @@ ObjectList::ObjectList(wxWindow* parent) : { wxDataViewItemArray sels; GetSelections(sels); - if (sels.front() == m_last_selected_item) + if (! sels.empty() && sels.front() == m_last_selected_item) m_last_selected_item = sels.back(); else m_last_selected_item = event.GetItem(); @@ -267,7 +267,8 @@ void ObjectList::create_objects_ctrl() wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // column Extruder of the view control: - AppendColumn(create_objects_list_extruder_column(4)); + AppendColumn(new wxDataViewColumn(_(L("Extruder")), new BitmapChoiceRenderer(), + colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE)); // column ItemEditing of the view control: AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em, @@ -434,19 +435,6 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons (*m_objects)[obj_idx]->config; } -wxDataViewColumn* ObjectList::create_objects_list_extruder_column(size_t extruders_count) -{ - wxArrayString choices; - choices.Add(_(L("default"))); - for (int i = 1; i <= extruders_count; ++i) - choices.Add(wxString::Format("%d", i)); - wxDataViewChoiceRenderer *c = - new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); - wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, colExtruder, - 8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); - return column; -} - void ObjectList::update_extruder_values_for_items(const size_t max_extruder) { for (size_t i = 0; i < m_objects->size(); ++i) @@ -457,24 +445,24 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder) auto object = (*m_objects)[i]; wxString extruder; if (!object->config.has("extruder") || - object->config.option("extruder")->value > max_extruder) + size_t(object->config.option("extruder")->value) > max_extruder) extruder = _(L("default")); else extruder = wxString::Format("%d", object->config.option("extruder")->value); - m_objects_model->SetValue(extruder, item, colExtruder); + m_objects_model->SetExtruder(extruder, item); if (object->volumes.size() > 1) { for (size_t id = 0; id < object->volumes.size(); id++) { item = m_objects_model->GetItemByVolumeId(i, id); if (!item) continue; if (!object->volumes[id]->config.has("extruder") || - object->volumes[id]->config.option("extruder")->value > max_extruder) + size_t(object->volumes[id]->config.option("extruder")->value) > max_extruder) extruder = _(L("default")); else extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); - m_objects_model->SetValue(extruder, item, colExtruder); + m_objects_model->SetExtruder(extruder, item); } } } @@ -486,19 +474,13 @@ void ObjectList::update_objects_list_extruder_column(size_t extruders_count) if (printer_technology() == ptSLA) extruders_count = 1; - wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(colExtruder)->GetRenderer()); - if (ch_render->GetChoices().GetCount() - 1 == extruders_count) - return; - m_prevent_update_extruder_in_config = true; if (m_objects && extruders_count > 1) update_extruder_values_for_items(extruders_count); - // delete old extruder column - DeleteColumn(GetColumn(colExtruder)); - // insert new created extruder column - InsertColumn(colExtruder, create_objects_list_extruder_column(extruders_count)); + update_extruder_colors(); + // set show/hide for this column set_extruder_column_hidden(extruders_count <= 1); //a workaround for a wrong last column width updating under OSX @@ -507,6 +489,11 @@ void ObjectList::update_objects_list_extruder_column(size_t extruders_count) m_prevent_update_extruder_in_config = false; } +void ObjectList::update_extruder_colors() +{ + m_objects_model->UpdateColumValues(colExtruder); +} + void ObjectList::set_extruder_column_hidden(const bool hide) const { GetColumn(colExtruder)->SetHidden(hide); @@ -535,14 +522,10 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) m_config = &get_item_config(item); } - wxVariant variant; - m_objects_model->GetValue(variant, item, colExtruder); - const wxString selection = variant.GetString(); - - if (!m_config || selection.empty()) + if (!m_config) return; - const int extruder = /*selection.size() > 1 ? 0 : */atoi(selection.c_str()); + const int extruder = m_objects_model->GetExtruderNumber(item); m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); // update scene @@ -795,7 +778,13 @@ void ObjectList::OnChar(wxKeyEvent& event) void ObjectList::OnContextMenu(wxDataViewEvent&) { - list_manipulation(true); + // Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true; + if (!evt_context_menu) + canvas->mouse_up_cleanup(); + + list_manipulation(evt_context_menu); } void ObjectList::list_manipulation(bool evt_context_menu/* = false*/) @@ -805,6 +794,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/) const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); + if (m_extruder_editor) + m_extruder_editor->Hide(); + /* Note: Under OSX right click doesn't send "selection changed" event. * It means that Selection() will be return still previously selected item. * Thus under OSX we should force UnselectAll(), when item and col are nullptr, @@ -853,6 +845,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/) fix_through_netfabb(); } } + // workaround for extruder editing under OSX + else if (wxOSX && evt_context_menu && title == _("Extruder")) + extruder_editing(); #ifndef __WXMSW__ GetMainWindow()->SetToolTip(""); // hide tooltip @@ -894,6 +889,74 @@ void ObjectList::show_context_menu(const bool evt_context_menu) wxGetApp().plater()->PopupMenu(menu); } +void ObjectList::extruder_editing() +{ + wxDataViewItem item = GetSelection(); + if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) + return; + + std::vector icons = get_extruder_color_icons(); + if (icons.empty()) + return; + + const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5; + + wxPoint pos = get_mouse_position_in_control(); + wxSize size = wxSize(column_width, -1); + pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5; + pos.y -= GetTextExtent("m").y; + + if (!m_extruder_editor) + m_extruder_editor = new wxBitmapComboBox(this, wxID_ANY, wxEmptyString, pos, size, + 0, nullptr, wxCB_READONLY); + else + { + m_extruder_editor->SetPosition(pos); + m_extruder_editor->SetMinSize(size); + m_extruder_editor->SetSize(size); + m_extruder_editor->Clear(); + m_extruder_editor->Show(); + } + + int i = 0; + for (wxBitmap* bmp : icons) { + if (i == 0) { + m_extruder_editor->Append(_(L("default")), *bmp); + ++i; + } + + m_extruder_editor->Append(wxString::Format("%d", i), *bmp); + ++i; + } + m_extruder_editor->SetSelection(m_objects_model->GetExtruderNumber(item)); + + auto set_extruder = [this]() + { + wxDataViewItem item = GetSelection(); + if (!item) return; + + const int selection = m_extruder_editor->GetSelection(); + if (selection >= 0) + m_objects_model->SetExtruder(m_extruder_editor->GetString(selection), item); + + m_extruder_editor->Hide(); + }; + + // to avoid event propagation to other sidebar items + m_extruder_editor->Bind(wxEVT_COMBOBOX, [set_extruder](wxCommandEvent& evt) + { + set_extruder(); + evt.StopPropagation(); + }); + /* + m_extruder_editor->Bind(wxEVT_KILL_FOCUS, [set_extruder](wxFocusEvent& evt) + { + set_extruder(); + evt.Skip(); + });*/ + +} + void ObjectList::copy() { // if (m_selection_mode & smLayer) @@ -1514,6 +1577,12 @@ void ObjectList::append_menu_item_export_stl(wxMenu* menu) const menu->AppendSeparator(); } +void ObjectList::append_menu_item_reload_from_disk(wxMenu* menu) const +{ + append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), + [this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater()); +} + void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const { const wxString name = _(L("Change extruder")); @@ -1563,6 +1632,7 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) append_menu_items_osx(menu); #endif // __WXOSX__ + append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); append_menu_item_scale_selection_to_fit_print_volume(menu); @@ -1586,6 +1656,7 @@ void ObjectList::create_sla_object_popupmenu(wxMenu *menu) append_menu_items_osx(menu); #endif // __WXOSX__ + append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); // rest of a object_sla_menu will be added later in: @@ -1598,8 +1669,9 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) append_menu_items_osx(menu); #endif // __WXOSX__ - append_menu_item_fix_through_netfabb(menu); + append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); + append_menu_item_fix_through_netfabb(menu); append_menu_item_split(menu); @@ -2259,6 +2331,7 @@ void ObjectList::changed_object(const int obj_idx/* = -1*/) const void ObjectList::part_selection_changed() { + if (m_extruder_editor) m_extruder_editor->Hide(); int obj_idx = -1; int volume_id = -1; m_config = nullptr; @@ -2341,7 +2414,8 @@ void ObjectList::part_selection_changed() wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); if (item) { - wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); + // wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); + wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id)); } } @@ -2547,7 +2621,7 @@ void ObjectList::delete_from_model_and_list(const std::vector& it (*m_objects)[item->obj_idx]->config.has("extruder")) { const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option("extruder")->value); - m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), colExtruder); + m_objects_model->SetExtruder(extruder, m_objects_model->GetItemById(item->obj_idx)); } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); } @@ -3822,7 +3896,7 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const /* We can change extruder for Object/Volume only. * So, if Instance is selected, get its Object item and change it */ - m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, colExtruder); + m_objects_model->SetExtruder(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 4dd618a90b..0874343b6e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -12,6 +12,7 @@ #include "wxExtensions.hpp" class wxBoxSizer; +class wxBitmapComboBox; class wxMenuItem; class ObjectDataViewModel; class MenuWithSeparators; @@ -140,6 +141,8 @@ private: DynamicPrintConfig *m_config {nullptr}; std::vector *m_objects{ nullptr }; + wxBitmapComboBox *m_extruder_editor { nullptr }; + std::vector m_bmp_vector; t_layer_config_ranges m_layer_config_ranges_cache; @@ -183,8 +186,8 @@ public: void create_objects_ctrl(); void create_popup_menus(); - wxDataViewColumn* create_objects_list_extruder_column(size_t extruders_count); void update_objects_list_extruder_column(size_t extruders_count); + void update_extruder_colors(); // show/hide "Extruder" column for Objects List void set_extruder_column_hidden(const bool hide) const; // update extruder in current config @@ -210,6 +213,7 @@ public: void selection_changed(); void show_context_menu(const bool evt_context_menu); + void extruder_editing(); #ifndef __WXOSX__ void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ @@ -233,7 +237,8 @@ public: wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent); void append_menu_items_osx(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); - void append_menu_item_export_stl(wxMenu* menu) const ; + void append_menu_item_export_stl(wxMenu* menu) const; + void append_menu_item_reload_from_disk(wxMenu* menu) const; void append_menu_item_change_extruder(wxMenu* menu) const; void append_menu_item_delete(wxMenu* menu); void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 2295ac0b6e..4ecab8a0f1 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -112,40 +112,33 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo) combo->SetValue(selection); } +static void set_font_and_background_style(wxWindow* win, const wxFont& font) +{ + win->SetFont(font); + win->SetBackgroundStyle(wxBG_STYLE_PAINT); +} ObjectManipulation::ObjectManipulation(wxWindow* parent) : OG_Settings(parent, true) -#ifndef __APPLE__ - , m_focused_option("") -#endif // __APPLE__ { m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation"); - m_og->set_name(_(L("Object Manipulation"))); - m_og->label_width = 12;//125; - m_og->set_grid_vgap(5); - - m_og->m_on_change = std::bind(&ObjectManipulation::on_change, this, std::placeholders::_1, std::placeholders::_2); - m_og->m_fill_empty_value = std::bind(&ObjectManipulation::on_fill_empty_value, this, std::placeholders::_1); - m_og->m_set_focus = [this](const std::string& opt_key) - { -#ifndef __APPLE__ - m_focused_option = opt_key; -#endif // __APPLE__ + // Load bitmaps to be used for the mirroring buttons: + m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); + m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); + m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); - // needed to show the visual hints in 3D scene - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true); - }; + const int border = wxOSX ? 0 : 4; + const int em = wxGetApp().em_unit(); + m_main_grid_sizer = new wxFlexGridSizer(2, 3, 3); // "Name/label", "String name / Editors" + m_main_grid_sizer->SetFlexibleDirection(wxBOTH); - ConfigOptionDef def; + // Add "Name" label with warning icon + auto sizer = new wxBoxSizer(wxHORIZONTAL); - Line line = Line{ "Name", "Object name" }; - - auto manifold_warning_icon = [this](wxWindow* parent) { - m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); - - if (is_windows10()) - m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e) + m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + if (is_windows10()) + m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent& e) { // if object/sub-object has no errors if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData()) @@ -155,248 +148,269 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list()); }); - return m_fix_throught_netfab_bitmap; + sizer->Add(m_fix_throught_netfab_bitmap); + + auto name_label = new wxStaticText(m_parent, wxID_ANY, _(L("Name"))+":"); + set_font_and_background_style(name_label, wxGetApp().normal_font()); + name_label->SetToolTip(_(L("Object name"))); + sizer->Add(name_label); + + m_main_grid_sizer->Add(sizer); + + // Add name of the item + const wxSize name_size = wxSize(20 * em, wxDefaultCoord); + m_item_name = new wxStaticText(m_parent, wxID_ANY, "", wxDefaultPosition, name_size, wxST_ELLIPSIZE_MIDDLE); + set_font_and_background_style(m_item_name, wxGetApp().bold_font()); + + m_main_grid_sizer->Add(m_item_name, 0, wxEXPAND); + + // Add labels grid sizer + m_labels_grid_sizer = new wxFlexGridSizer(1, 3, 3); // "Name/label", "String name / Editors" + m_labels_grid_sizer->SetFlexibleDirection(wxBOTH); + + // Add world local combobox + m_word_local_combo = create_word_local_combo(parent); + m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { + this->set_world_coordinates(evt.GetSelection() != 1); + }), m_word_local_combo->GetId()); + + // Small trick to correct layouting in different view_mode : + // Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden + m_word_local_combo_sizer = new wxBoxSizer(wxHORIZONTAL); + m_empty_str = new wxStaticText(parent, wxID_ANY, ""); + m_word_local_combo_sizer->Add(m_word_local_combo); + m_word_local_combo_sizer->Add(m_empty_str); + m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1))); + m_labels_grid_sizer->Add(m_word_local_combo_sizer); + + // Text trick to grid sizer layout: + // Height of labels should be equivalent to the edit boxes + int height = wxTextCtrl(parent, wxID_ANY, "Br").GetBestHeight(-1); +#ifdef __WXGTK__ + // On Linux button with bitmap has bigger height then regular button or regular TextCtrl + // It can cause a wrong alignment on show/hide of a reset buttons + const int bmp_btn_height = ScalableButton(parent, wxID_ANY, "undo") .GetBestHeight(-1); + if (bmp_btn_height > height) + height = bmp_btn_height; +#endif //__WXGTK__ + + auto add_label = [this, height](wxStaticText** label, const std::string& name, wxSizer* reciver = nullptr) + { + *label = new wxStaticText(m_parent, wxID_ANY, _(name) + ":"); + set_font_and_background_style(m_move_Label, wxGetApp().normal_font()); + + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->SetMinSize(wxSize(-1, height)); + sizer->Add(*label, 0, wxALIGN_CENTER_VERTICAL); + + if (reciver) + reciver->Add(sizer); + else + m_labels_grid_sizer->Add(sizer); + + m_rescalable_sizers.push_back(sizer); }; - line.near_label_widget = manifold_warning_icon; - def.label = ""; - def.gui_type = "legend"; - def.tooltip = L("Object name"); -#ifdef __APPLE__ - def.width = 20; -#else - def.width = 22; -#endif - def.set_default_value(new ConfigOptionString{ " " }); - line.append_option(Option(def, "object_name")); - m_og->append_line(line); + // Add labels + add_label(&m_move_Label, L("Position")); + add_label(&m_rotate_Label, L("Rotation")); - const int field_width = 5; + // additional sizer for lock and labels "Scale" & "Size" + sizer = new wxBoxSizer(wxHORIZONTAL); - // Mirror button size: - const int mirror_btn_width = 3; + m_lock_bnt = new LockButton(parent, wxID_ANY); + m_lock_bnt->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + event.Skip(); + wxTheApp->CallAfter([this]() { set_uniform_scaling(m_lock_bnt->IsLocked()); }); + }); + sizer->Add(m_lock_bnt, 0, wxALIGN_CENTER_VERTICAL); - // Legend for object modification - line = Line{ "", "" }; - def.label = ""; - def.type = coString; - def.width = field_width - mirror_btn_width;//field_width/*50*/; + auto v_sizer = new wxGridSizer(1, 3, 3); - // Load bitmaps to be used for the mirroring buttons: - m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); - m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); - m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); + add_label(&m_scale_Label, L("Scale"), v_sizer); + wxStaticText* size_Label {nullptr}; + add_label(&size_Label, L("Size"), v_sizer); + if (wxOSX) set_font_and_background_style(size_Label, wxGetApp().normal_font()); + sizer->Add(v_sizer, 0, wxLEFT, border); + m_labels_grid_sizer->Add(sizer); + m_main_grid_sizer->Add(m_labels_grid_sizer, 0, wxEXPAND); + + + // Add editors grid sizer + wxFlexGridSizer* editors_grid_sizer = new wxFlexGridSizer(5, 3, 3); // "Name/label", "String name / Editors" + editors_grid_sizer->SetFlexibleDirection(wxBOTH); + + // Add Axes labels with icons static const char axes[] = { 'X', 'Y', 'Z' }; for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) { const char label = axes[axis_idx]; - def.set_default_value(new ConfigOptionString{ std::string(" ") + label }); - Option option(def, std::string() + label + "_axis_legend"); + + wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label)); + set_font_and_background_style(axis_name, wxGetApp().bold_font()); + + sizer = new wxBoxSizer(wxHORIZONTAL); + // Under OSX we use font, smaller than default font, so + // there is a next trick for an equivalent layout of coordinates combobox and axes labels in they own sizers + if (wxOSX) + sizer->SetMinSize(-1, m_word_local_combo->GetBestHeight(-1)); + sizer->Add(axis_name, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); // We will add a button to toggle mirroring to each axis: - auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) { - wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); - btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); - btn->SetBitmapDisabled_(m_mirror_bitmap_hidden); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); + btn->SetBitmapDisabled_(m_mirror_bitmap_hidden); - m_mirror_buttons[axis_idx].first = btn; - m_mirror_buttons[axis_idx].second = mbShown; - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + m_mirror_buttons[axis_idx].first = btn; + m_mirror_buttons[axis_idx].second = mbShown; - btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) { - Axis axis = (Axis)(axis_idx + X); - if (m_mirror_buttons[axis_idx].second == mbHidden) - return; + sizer->AddStretchSpacer(2); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); - GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - Selection& selection = canvas->get_selection(); + btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) { + Axis axis = (Axis)(axis_idx + X); + if (m_mirror_buttons[axis_idx].second == mbHidden) + return; - if (selection.is_single_volume() || selection.is_single_modifier()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); + } + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()) { + GLVolume* volume = const_cast(selection.get_volume(idx)); + volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); } - else if (selection.is_single_full_instance()) { - for (unsigned int idx : selection.get_volume_idxs()){ - GLVolume* volume = const_cast(selection.get_volume(idx)); - volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); - } - } - else - return; + } + else + return; - // Update mirroring at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); - selection.synchronize_unselected_volumes(); - // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_mirror(L("Set Mirror")); - UpdateAndShow(true); - }); + // Update mirroring at the GLVolumes. + selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_volumes(); + // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + canvas->do_mirror(L("Set Mirror")); + UpdateAndShow(true); + }); - return sizer; - }; - - option.side_widget = mirror_button; - line.append_option(option); + editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL); } - line.near_label_widget = [this](wxWindow* parent) { - wxBitmapComboBox *combo = create_word_local_combo(parent); - combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent &evt) { this->set_world_coordinates(evt.GetSelection() != 1); }), combo->GetId()); - m_word_local_combo = combo; - return combo; - }; - m_og->append_line(line); - auto add_og_to_object_settings = [this, field_width](const std::string& option_name, const std::string& sidetext) + editors_grid_sizer->AddStretchSpacer(1); + editors_grid_sizer->AddStretchSpacer(1); + + // add EditBoxes + auto add_edit_boxes = [this, editors_grid_sizer](const std::string& opt_key, int axis) { - Line line = { _(option_name), "" }; - ConfigOptionDef def; - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat(0.0)); - def.width = field_width/*50*/; + ManipulationEditor* editor = new ManipulationEditor(this, opt_key, axis); + m_editors.push_back(editor); - if (option_name == "Scale") { - // Add "uniform scaling" button in front of "Scale" option - line.near_label_widget = [this](wxWindow* parent) { - auto btn = new LockButton(parent, wxID_ANY); - btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){ - event.Skip(); - wxTheApp->CallAfter([btn, this]() { set_uniform_scaling(btn->IsLocked()); }); - }); - m_lock_bnt = btn; - return btn; - }; - // Add reset scale button - auto reset_scale_button = [this](wxWindow* parent) { - auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); - btn->SetToolTip(_(L("Reset scale"))); - m_reset_scale_button = btn; - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale"))); - change_scale_value(0, 100.); - change_scale_value(1, 100.); - change_scale_value(2, 100.); - }); - return sizer; - }; - line.append_widget(reset_scale_button); - } - else if (option_name == "Rotation") { - // Add reset rotation button - auto reset_rotation_button = [this](wxWindow* parent) { - auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); - btn->SetToolTip(_(L("Reset rotation"))); - m_reset_rotation_button = btn; - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { - GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - Selection& selection = canvas->get_selection(); + editors_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL); + }; + + // add Units + auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit) + { + wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit)); + set_font_and_background_style(unit_text, wxGetApp().normal_font()); - if (selection.is_single_volume() || selection.is_single_modifier()) { - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - volume->set_volume_rotation(Vec3d::Zero()); - } - else if (selection.is_single_full_instance()) { - for (unsigned int idx : selection.get_volume_idxs()){ - GLVolume* volume = const_cast(selection.get_volume(idx)); - volume->set_instance_rotation(Vec3d::Zero()); - } - } - else - return; + // Unit text should be the same height as labels + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->SetMinSize(wxSize(-1, height)); + sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL); - // Update rotation at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); - selection.synchronize_unselected_volumes(); - // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_rotate(L("Reset Rotation")); - - UpdateAndShow(true); - }); - return sizer; - }; - line.append_widget(reset_rotation_button); - } - else if (option_name == "Position") { - // Add drop to bed button - auto drop_to_bed_button = [=](wxWindow* parent) { - auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); - btn->SetToolTip(_(L("Drop to bed"))); - m_drop_to_bed_button = btn; - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { - // ??? - GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - Selection& selection = canvas->get_selection(); - - if (selection.is_single_volume() || selection.is_single_modifier()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - - const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); - Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed"))); - change_position_value(0, diff.x()); - change_position_value(1, diff.y()); - change_position_value(2, diff.z()); - } - }); - return sizer; - }; - line.append_widget(drop_to_bed_button); - } - // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment - else if (option_name == "Size") { - line.near_label_widget = [this](wxWindow* parent) { - return new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, - create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize()); - }; - } - - const std::string lower_name = boost::algorithm::to_lower_copy(option_name); - - for (const char *axis : { "_x", "_y", "_z" }) { - if (axis[1] == 'z') - def.sidetext = sidetext; - Option option = Option(def, lower_name + axis); - option.opt.full_width = true; - line.append_option(option); - } - - return line; + editors_grid_sizer->Add(sizer); + m_rescalable_sizers.push_back(sizer); }; - // Settings table - m_og->sidetext_width = 3; - m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); - m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label); - m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label); - m_og->append_line(add_og_to_object_settings(L("Size"), "mm")); + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("position", axis_idx); + add_unit_text(L("mm")); - // call back for a rescale of button "Set uniform scale" - m_og->rescale_near_label_widget = [this](wxWindow* win) { - // rescale lock icon - auto *ctrl = dynamic_cast(win); - if (ctrl != nullptr) { - ctrl->msw_rescale(); - return; + // Add drop to bed button + m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); + m_drop_to_bed_button->SetToolTip(_(L("Drop to bed"))); + m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { + // ??? + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + + const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); + Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed"))); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); } + }); + editors_grid_sizer->Add(m_drop_to_bed_button); - if (win == m_fix_throught_netfab_bitmap) + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("rotation", axis_idx); + add_unit_text("°"); + + // Add reset rotation button + m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_rotation_button->SetToolTip(_(L("Reset rotation"))); + m_reset_rotation_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + volume->set_volume_rotation(Vec3d::Zero()); + } + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()) { + GLVolume* volume = const_cast(selection.get_volume(idx)); + volume->set_instance_rotation(Vec3d::Zero()); + } + } + else return; - // rescale "place" of the empty icon (to correct layout of the "Size" and "Scale") - if (dynamic_cast(win) != nullptr) - win->SetMinSize(create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize()); - }; + // Update rotation at the GLVolumes. + selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_volumes(); + // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + canvas->do_rotate(L("Reset Rotation")); + + UpdateAndShow(true); + }); + editors_grid_sizer->Add(m_reset_rotation_button); + + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("scale", axis_idx); + add_unit_text("%"); + + // Add reset scale button + m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_scale_button->SetToolTip(_(L("Reset scale"))); + m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale"))); + change_scale_value(0, 100.); + change_scale_value(1, 100.); + change_scale_value(2, 100.); + }); + editors_grid_sizer->Add(m_reset_scale_button); + + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("size", axis_idx); + add_unit_text("mm"); + editors_grid_sizer->AddStretchSpacer(1); + + m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); + + m_og->sizer->Clear(true); + m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border); } - - void ObjectManipulation::Show(const bool show) { @@ -407,9 +421,9 @@ void ObjectManipulation::Show(const bool show) if (show && wxGetApp().get_mode() != comSimple) { // Show the label and the name of the STL in simple mode only. // Label "Name: " - m_og->get_grid_sizer()->Show(size_t(0), false); + m_main_grid_sizer->Show(size_t(0), false); // The actual name of the STL. - m_og->get_grid_sizer()->Show(size_t(1), false); + m_main_grid_sizer->Show(size_t(1), false); } } @@ -417,6 +431,7 @@ void ObjectManipulation::Show(const bool show) // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only. bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; m_word_local_combo->Show(show_world_local_combo); + m_empty_str->Show(!show_world_local_combo); } } @@ -522,30 +537,40 @@ void ObjectManipulation::update_if_dirty() if (label_cache != new_label_localized) { label_cache = new_label_localized; widget->SetLabel(new_label_localized); + if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font()); } }; update_label(m_cache.move_label_string, m_new_move_label_string, m_move_Label); update_label(m_cache.rotate_label_string, m_new_rotate_label_string, m_rotate_Label); update_label(m_cache.scale_label_string, m_new_scale_label_string, m_scale_Label); - char axis[2] = "x"; - for (int i = 0; i < 3; ++ i, ++ axis[0]) { - auto update = [this, i, &axis](Vec3d &cached, Vec3d &cached_rounded, const char *key, const Vec3d &new_value) { + enum ManipulationEditorKey + { + mePosition = 0, + meRotation, + meScale, + meSize + }; + + for (int i = 0; i < 3; ++ i) { + auto update = [this, i](Vec3d &cached, Vec3d &cached_rounded, ManipulationEditorKey key_id, const Vec3d &new_value) { wxString new_text = double_to_string(new_value(i), 2); double new_rounded; new_text.ToDouble(&new_rounded); if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) { cached_rounded(i) = new_rounded; - m_og->set_value(std::string(key) + axis, new_text); + const int id = key_id*3+i; + if (id >= 0) m_editors[id]->set_value(new_text); } cached(i) = new_value(i); }; - update(m_cache.position, m_cache.position_rounded, "position_", m_new_position); - update(m_cache.scale, m_cache.scale_rounded, "scale_", m_new_scale); - update(m_cache.size, m_cache.size_rounded, "size_", m_new_size); - update(m_cache.rotation, m_cache.rotation_rounded, "rotation_", m_new_rotation); + update(m_cache.position, m_cache.position_rounded, mePosition, m_new_position); + update(m_cache.scale, m_cache.scale_rounded, meScale, m_new_scale); + update(m_cache.size, m_cache.size_rounded, meSize, m_new_size); + update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } + if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_(L("You cannot use non-uniform scaling mode for multiple objects/parts selection"))); @@ -607,7 +632,11 @@ void ObjectManipulation::update_reset_buttons_visibility() show_drop_to_bed = (std::abs(min_z) > EPSILON); } - wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{ + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { + // There is a case (under OSX), when this function is called after the Manipulation panel is hidden + // So, let check if Manipulation panel is still shown for this moment + if (!this->IsShown()) + return; m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); m_drop_to_bed_button->Show(show_drop_to_bed); @@ -673,20 +702,18 @@ void ObjectManipulation::update_mirror_buttons_visibility() #ifndef __APPLE__ void ObjectManipulation::emulate_kill_focus() { - if (m_focused_option.empty()) + if (!m_focused_editor) return; - // we need to use a copy because the value of m_focused_option is modified inside on_change() and on_fill_empty_value() - std::string option = m_focused_option; - - // see TextCtrl::propagate_value() - if (static_cast(m_og->get_fieldc(option, 0)->getWindow())->GetValue().empty()) - on_fill_empty_value(option); - else - on_change(option, 0); + m_focused_editor->kill_focus(this); } #endif // __APPLE__ +void ObjectManipulation::update_item_name(const wxString& item_name) +{ + m_item_name->SetLabel(item_name); +} + void ObjectManipulation::update_warning_icon_state(const wxString& tooltip) { m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); @@ -817,76 +844,21 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } -void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) +void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) { - Field* field = m_og->get_field(opt_key); - bool enter_pressed = (field != nullptr) && field->get_enter_pressed(); - if (!enter_pressed) - { - // if the change does not come from the user pressing the ENTER key - // we need to hide the visual hints in 3D scene - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); - -#ifndef __APPLE__ - m_focused_option = ""; -#endif // __APPLE__ - } - else - // if the change comes from the user pressing the ENTER key, restore the key state - field->set_enter_pressed(false); - if (!m_cache.is_valid()) return; - int axis = opt_key.back() - 'x'; - double new_value = boost::any_cast(m_og->get_value(opt_key)); - - if (boost::starts_with(opt_key, "position_")) + if (opt_key == "position") change_position_value(axis, new_value); - else if (boost::starts_with(opt_key, "rotation_")) + else if (opt_key == "rotation") change_rotation_value(axis, new_value); - else if (boost::starts_with(opt_key, "scale_")) + else if (opt_key == "scale") change_scale_value(axis, new_value); - else if (boost::starts_with(opt_key, "size_")) + else if (opt_key == "size") change_size_value(axis, new_value); } -void ObjectManipulation::on_fill_empty_value(const std::string& opt_key) -{ - // needed to hide the visual hints in 3D scene - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); -#ifndef __APPLE__ - m_focused_option = ""; -#endif // __APPLE__ - - if (!m_cache.is_valid()) - return; - - const Vec3d *vec = nullptr; - Vec3d *rounded = nullptr; - if (boost::starts_with(opt_key, "position_")) { - vec = &m_cache.position; - rounded = &m_cache.position_rounded; - } else if (boost::starts_with(opt_key, "rotation_")) { - vec = &m_cache.rotation; - rounded = &m_cache.rotation_rounded; - } else if (boost::starts_with(opt_key, "scale_")) { - vec = &m_cache.scale; - rounded = &m_cache.scale_rounded; - } else if (boost::starts_with(opt_key, "size_")) { - vec = &m_cache.size; - rounded = &m_cache.size_rounded; - } else - assert(false); - - if (vec != nullptr) { - int axis = opt_key.back() - 'x'; - wxString new_text = double_to_string((*vec)(axis)); - m_og->set_value(opt_key, new_text); - new_text.ToDouble(&(*rounded)(axis)); - } -} - void ObjectManipulation::set_uniform_scaling(const bool new_value) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); @@ -923,7 +895,10 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) void ObjectManipulation::msw_rescale() { + const int em = wxGetApp().em_unit(); + m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord)); msw_rescale_word_local_combo(m_word_local_combo); + m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1))); m_manifold_warning_bmp.msw_rescale(); const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText(); @@ -936,12 +911,120 @@ void ObjectManipulation::msw_rescale() m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); m_drop_to_bed_button->msw_rescale(); + m_lock_bnt->msw_rescale(); for (int id = 0; id < 3; ++id) m_mirror_buttons[id].first->msw_rescale(); + // rescale label-heights + // Text trick to grid sizer layout: + // Height of labels should be equivalent to the edit boxes + const int height = wxTextCtrl(parent(), wxID_ANY, "Br").GetBestHeight(-1); + for (wxBoxSizer* sizer : m_rescalable_sizers) + sizer->SetMinSize(wxSize(-1, height)); + + // rescale edit-boxes + for (ManipulationEditor* editor : m_editors) + editor->msw_rescale(); + get_og()->msw_rescale(); } +static const char axes[] = { 'x', 'y', 'z' }; +ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, + const std::string& opt_key, + int axis) : + wxTextCtrl(parent->parent(), wxID_ANY, wxEmptyString, wxDefaultPosition, + wxSize(5*int(wxGetApp().em_unit()), wxDefaultCoord), wxTE_PROCESS_ENTER), + m_opt_key(opt_key), + m_axis(axis) +{ + set_font_and_background_style(this, wxGetApp().normal_font()); +#ifdef __WXOSX__ + this->OSXDisableAllSmartSubstitutions(); +#endif // __WXOSX__ + + // A name used to call handle_sidebar_focus_event() + m_full_opt_name = m_opt_key+"_"+axes[axis]; + + // Reset m_enter_pressed flag to _false_, when value is editing + this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId()); + + this->Bind(wxEVT_TEXT_ENTER, [this, parent](wxEvent&) + { + m_enter_pressed = true; + parent->on_change(m_opt_key, m_axis, get_value()); + }, this->GetId()); + + this->Bind(wxEVT_KILL_FOCUS, [this, parent](wxFocusEvent& e) + { + parent->set_focused_editor(nullptr); + + if (!m_enter_pressed) + kill_focus(parent); + + e.Skip(); + }, this->GetId()); + + this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) + { + parent->set_focused_editor(this); + + // needed to show the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, true); + e.Skip(); + }, this->GetId()); + + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + this->SetSelection(-1, -1); //select all + event.Skip(); + })); +} + +void ManipulationEditor::msw_rescale() +{ + const int em = wxGetApp().em_unit(); + SetMinSize(wxSize(5 * em, wxDefaultCoord)); +} + +double ManipulationEditor::get_value() +{ + wxString str = GetValue(); + + double value; + // Replace the first occurence of comma in decimal number. + str.Replace(",", ".", false); + if (str == ".") + value = 0.0; + + if ((str.IsEmpty() || !str.ToCDouble(&value)) && !m_valid_value.IsEmpty()) { + str = m_valid_value; + SetValue(str); + str.ToCDouble(&value); + } + + return value; +} + +void ManipulationEditor::set_value(const wxString& new_value) +{ + if (new_value.IsEmpty()) + return; + m_valid_value = new_value; + SetValue(m_valid_value); +} + +void ManipulationEditor::kill_focus(ObjectManipulation* parent) +{ + parent->on_change(m_opt_key, m_axis, get_value()); + + // if the change does not come from the user pressing the ENTER key + // we need to hide the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, false); +} + } //namespace GUI } //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index e4e190b5bc..e6f99ab2a1 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -16,6 +16,29 @@ namespace GUI { class Selection; +class ObjectManipulation; +class ManipulationEditor : public wxTextCtrl +{ + std::string m_opt_key; + int m_axis; + bool m_enter_pressed { false }; + wxString m_valid_value {wxEmptyString}; + + std::string m_full_opt_name; + +public: + ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, int axis); + ~ManipulationEditor() {} + + void msw_rescale(); + void set_value(const wxString& new_value); + void kill_focus(ObjectManipulation *parent); + +private: + double get_value(); +}; + + class ObjectManipulation : public OG_Settings { struct Cache @@ -53,6 +76,9 @@ class ObjectManipulation : public OG_Settings wxStaticText* m_scale_Label = nullptr; wxStaticText* m_rotate_Label = nullptr; + wxStaticText* m_item_name = nullptr; + wxStaticText* m_empty_str = nullptr; + // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button = nullptr; ScalableButton* m_reset_rotation_button = nullptr; @@ -81,7 +107,7 @@ class ObjectManipulation : public OG_Settings Vec3d m_new_rotation; Vec3d m_new_scale; Vec3d m_new_size; - bool m_new_enabled; + bool m_new_enabled {true}; bool m_uniform_scale {true}; // Does the object manipulation panel work in World or Local coordinates? bool m_world_coordinates = true; @@ -92,10 +118,19 @@ class ObjectManipulation : public OG_Settings wxStaticBitmap* m_fix_throught_netfab_bitmap; #ifndef __APPLE__ - // Currently focused option name (empty if none) - std::string m_focused_option; + // Currently focused editor (nullptr if none) + ManipulationEditor* m_focused_editor {nullptr}; #endif // __APPLE__ + wxFlexGridSizer* m_main_grid_sizer; + wxFlexGridSizer* m_labels_grid_sizer; + + // sizers, used for msw_rescale + wxBoxSizer* m_word_local_combo_sizer; + std::vector m_rescalable_sizers; + + std::vector m_editors; + public: ObjectManipulation(wxWindow* parent); ~ObjectManipulation() {} @@ -122,8 +157,15 @@ public: void emulate_kill_focus(); #endif // __APPLE__ + void update_item_name(const wxString &item_name); void update_warning_icon_state(const wxString& tooltip); void msw_rescale(); + void on_change(const std::string& opt_key, int axis, double new_value); + void set_focused_editor(ManipulationEditor* focused_editor) { +#ifndef __APPLE__ + m_focused_editor = focused_editor; +#endif // __APPLE__ + } private: void reset_settings_value(); @@ -140,9 +182,6 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; - - void on_change(t_config_option_key opt_key, const boost::any& value); - void on_fill_empty_value(const std::string& opt_key); }; }} diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 7c761ed5fe..d89ac1bcbb 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -221,6 +221,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_choice_view_type->Append(_(L("Height"))); m_choice_view_type->Append(_(L("Width"))); m_choice_view_type->Append(_(L("Speed"))); + m_choice_view_type->Append(_(L("Fan speed"))); m_choice_view_type->Append(_(L("Volumetric flow rate"))); m_choice_view_type->Append(_(L("Tool"))); m_choice_view_type->Append(_(L("Color Print"))); @@ -374,6 +375,8 @@ void Preview::load_print(bool keep_z_range) load_print_as_fff(keep_z_range); else if (tech == ptSLA) load_print_as_sla(); + + Layout(); } void Preview::reload_print(bool keep_volumes) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 16fa30fa78..25dab03360 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -417,6 +417,9 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { + // used to set a right up event as processed when needed + static bool pending_right_up = false; + Point pos(evt.GetX(), evt.GetY()); Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); @@ -442,7 +445,14 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) else if (evt.MiddleUp()) m_mouse_capture.middle = false; else if (evt.RightUp()) + { m_mouse_capture.right = false; + if (pending_right_up) + { + pending_right_up = false; + processed = true; + } + } else if (evt.Dragging() && m_mouse_capture.any()) // if the button down was done on this toolbar, prevent from dragging into the scene processed = true; @@ -473,8 +483,12 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) } } else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown)) + { + // we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object + pending_right_up = true; // event was taken care of by the SlaSupports gizmo processed = true; + } else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports)) // don't allow dragging objects with the Sla gizmo on processed = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index f649c98b25..0defb13483 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -114,8 +114,17 @@ public: m_serializing = true; + // Following is needed to know which to be turn on, but not actually modify + // m_current prematurely, so activate_gizmo is not confused. + EType old_current = m_current; ar(m_current); + EType new_current = m_current; + m_current = old_current; + // activate_gizmo call sets m_current and calls set_state for the gizmo + // it does nothing in case the gizmo is already activated + // it can safely be called for Undefined gizmo + activate_gizmo(new_current); if (m_current != Undefined) m_gizmos[m_current]->load(ar); } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7c36f3665d..b76110a874 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const const PrintObjects& objects = m_plater->sla_print().objects(); for (const SLAPrintObject* object : objects) { - if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree)) + if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree)) { can_export = true; break; @@ -682,6 +682,11 @@ void MainFrame::init_menubar() helpMenu->AppendSeparator(); append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")), [this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG + helpMenu->AppendSeparator(); + append_menu_item(helpMenu, wxID_ANY, _(L("DEBUG gcode thumbnails")), _(L("DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails")), + [this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); }); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG } // menubar diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 698c1e0348..8b6f5bc304 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -233,7 +233,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n add_undo_buttuns_to_sizer(sizer, field); if (is_window_field(field)) - sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) | + sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) | wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2); if (is_sizer_field(field)) sizer->Add(field->getSizer(), 1, /*(*/option.opt.full_width ? wxEXPAND : /*0) |*/ wxALIGN_CENTER_VERTICAL, 0); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1042499b2d..8cc47b45a0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,9 @@ #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#if ENABLE_THUMBNAIL_GENERATOR +#include "libslic3r/GCode/ThumbnailData.hpp" +#endif // ENABLE_THUMBNAIL_GENERATOR #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" @@ -71,6 +75,7 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" +#include "../Utils/Thread.hpp" #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" @@ -81,6 +86,11 @@ using Slic3r::_3DScene; using Slic3r::Preset; using Slic3r::PrintHostJob; +#if ENABLE_THUMBNAIL_GENERATOR +static const std::vector < std::pair> THUMBNAIL_SIZE_FFF = { { 240, 320 }, { 220, 165 }, { 16, 16 } }; +static const std::vector> THUMBNAIL_SIZE_SLA = { { 800, 480 } }; +static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; +#endif // ENABLE_THUMBNAIL_GENERATOR namespace Slic3r { namespace GUI { @@ -174,7 +184,7 @@ void ObjectInfo::msw_rescale() manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation")); } -enum SlisedInfoIdx +enum SlicedInfoIdx { siFilament_m, siFilament_mm3, @@ -191,7 +201,7 @@ class SlicedInfo : public wxStaticBoxSizer { public: SlicedInfo(wxWindow *parent); - void SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label=""); + void SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label=""); private: std::vector> info_vec; @@ -229,7 +239,7 @@ SlicedInfo::SlicedInfo(wxWindow *parent) : this->Show(false); } -void SlicedInfo::SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/) +void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/) { const bool show = text != "N/A"; if (show) @@ -251,11 +261,18 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * auto selected_item = this->GetSelection(); auto marker = reinterpret_cast(this->GetClientData(selected_item)); - if (marker == LABEL_ITEM_MARKER || marker == LABEL_ITEM_CONFIG_WIZARD) { + if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->last_selected); evt.StopPropagation(); - if (marker == LABEL_ITEM_CONFIG_WIZARD) - wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); + if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { + ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; + switch (marker) { + case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; + case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; + case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; + } + wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); + } } else if ( this->last_selected != selected_item || wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { this->last_selected = selected_item; @@ -521,12 +538,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : const std::vector &init_matrix = (project_config.option("wiping_volumes_matrix"))->values; const std::vector &init_extruders = (project_config.option("wiping_volumes_extruders"))->values; - const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; - std::vector extruder_colours = (config->option("extruder_colour"))->values; - const std::vector& filament_colours = (wxGetApp().plater()->get_plater_config()->option("filament_colour"))->values; - for (size_t i=0; i extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); WipingDialog dlg(parent, cast(init_matrix), cast(init_extruders), extruder_colours); @@ -1215,7 +1227,7 @@ void Sidebar::update_sliced_info_sizer() } // if there is a wipe tower, insert number of toolchanges info into the array: - p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", p->plater->fff_print().wipe_tower_data().number_of_toolchanges) : "N/A"); + p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A"); // Hide non-FFF sliced info parameters p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); @@ -1377,6 +1389,9 @@ struct Plater::priv Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; +#if ENABLE_THUMBNAIL_GENERATOR + std::vector thumbnail_data; +#endif // ENABLE_THUMBNAIL_GENERATOR // GUI elements wxSizer* panel_sizer{ nullptr }; @@ -1443,7 +1458,7 @@ struct Plater::priv class Job : public wxEvtHandler { int m_range = 100; - std::future m_ftr; + boost::thread m_thread; priv * m_plater = nullptr; std::atomic m_running{false}, m_canceled{false}; bool m_finalized = false; @@ -1484,7 +1499,8 @@ struct Plater::priv // Do a full refresh of scene tree, including regenerating // all the GLVolumes. FIXME The update function shall just // reload the modified matrices. - if (!was_canceled()) plater().update((unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH); + if (!was_canceled()) + plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); } public: @@ -1513,9 +1529,9 @@ struct Plater::priv } Job(const Job &) = delete; - Job(Job &&) = default; + Job(Job &&) = delete; Job &operator=(const Job &) = delete; - Job &operator=(Job &&) = default; + Job &operator=(Job &&) = delete; virtual void process() = 0; @@ -1539,7 +1555,7 @@ struct Plater::priv wxBeginBusyCursor(); try { // Execute the job - m_ftr = std::async(std::launch::async, &Job::run, this); + m_thread = create_thread([this] { this->run(); }); } catch (std::exception &) { update_status(status_range(), _(L("ERROR: not enough resources to " @@ -1555,16 +1571,15 @@ struct Plater::priv // 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) const + bool join(int timeout_ms = 0) { - if (!m_ftr.valid()) return true; - + if (!m_thread.joinable()) return true; + if (timeout_ms <= 0) - m_ftr.wait(); - else if (m_ftr.wait_for(std::chrono::milliseconds( - timeout_ms)) == std::future_status::timeout) + m_thread.join(); + else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) return false; - + return true; } @@ -1594,7 +1609,8 @@ struct Plater::priv size_t count = 0; // To know how much space to reserve for (auto obj : model.objects) count += obj->instances.size(); - m_selected.clear(), m_unselected.clear(); + m_selected.clear(); + m_unselected.clear(); m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); } @@ -1608,11 +1624,12 @@ struct Plater::priv // Set up arrange polygon for a ModelInstance and Wipe tower template ArrangePolygon get_arrange_poly(T *obj) const { ArrangePolygon ap = obj->get_arrange_polygon(); - ap.priority = 0; - ap.bed_idx = ap.translation.x() / bed_stride(); - ap.setter = [obj, this](const ArrangePolygon &p) { + ap.priority = 0; + ap.bed_idx = ap.translation.x() / bed_stride(); + ap.setter = [obj, this](const ArrangePolygon &p) { if (p.is_arranged()) { - auto t = p.translation; t.x() += p.bed_idx * bed_stride(); + auto t = p.translation; + t.x() += p.bed_idx * bed_stride(); obj->apply_arrange_result(t, p.rotation); } }; @@ -1643,7 +1660,8 @@ struct Plater::priv obj_sel(model.objects.size(), nullptr); for (auto &s : plater().get_selection().get_content()) - if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second; + if (s.first < int(obj_sel.size())) + obj_sel[size_t(s.first)] = &s.second; // Go through the objects and check if inside the selection for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { @@ -1653,7 +1671,8 @@ struct Plater::priv std::vector inst_sel(mo->instances.size(), false); if (instlist) - for (auto inst_id : *instlist) inst_sel[inst_id] = true; + for (auto inst_id : *instlist) + inst_sel[size_t(inst_id)] = true; for (size_t i = 0; i < inst_sel.size(); ++i) { ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); @@ -1925,6 +1944,11 @@ struct Plater::priv bool can_fix_through_netfabb() const; bool can_set_instance_to_object() const; bool can_mirror() const; + bool can_reload_from_disk() const; + +#if ENABLE_THUMBNAIL_GENERATOR + void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background); +#endif // ENABLE_THUMBNAIL_GENERATOR void msw_rescale_object_menu(); @@ -1961,7 +1985,6 @@ private: * */ std::string m_last_fff_printer_profile_name; std::string m_last_sla_printer_profile_name; - bool m_update_objects_list_on_loading{ true }; }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -1994,6 +2017,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); background_process.set_gcode_preview_data(&gcode_preview_data); +#if ENABLE_THUMBNAIL_GENERATOR + background_process.set_thumbnail_data(&thumbnail_data); +#endif // ENABLE_THUMBNAIL_GENERATOR background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); // Default printer technology for default config. @@ -2487,11 +2513,8 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode _(L("Object too large?"))); } - if (m_update_objects_list_on_loading) - { - for (const size_t idx : obj_idxs) { - wxGetApp().obj_list()->add_object_to_list(idx); - } + for (const size_t idx : obj_idxs) { + wxGetApp().obj_list()->add_object_to_list(idx); } update(); @@ -2782,9 +2805,8 @@ void Plater::priv::ArrangeJob::process() { try { arrangement::arrange(m_selected, m_unselected, min_d, bedshape, [this, count](unsigned st) { - if (st > - 0) // will not finalize after last one - update_status(count - st, arrangestr); + if (st > 0) // will not finalize after last one + update_status(int(count - st), arrangestr); }, [this]() { return was_canceled(); }); } catch (std::exception & /*e*/) { @@ -3042,6 +3064,34 @@ bool Plater::priv::restart_background_process(unsigned int state) ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) || (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 || (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { +#if ENABLE_THUMBNAIL_GENERATOR + if (((state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) == 0) && + (this->background_process.state() != BackgroundSlicingProcess::STATE_RUNNING)) + { + // update thumbnail data + if (this->printer_technology == ptFFF) + { + // for ptFFF we need to generate the thumbnails before the export of gcode starts + this->thumbnail_data.clear(); + for (const std::pair& size : THUMBNAIL_SIZE_FFF) + { + this->thumbnail_data.push_back(ThumbnailData()); + generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, true, false); + } + } + else if (this->printer_technology == ptSLA) + { + // for ptSLA generate thumbnails without supports and pad (not yet calculated) + // to render also supports and pad see on_slicing_update() + this->thumbnail_data.clear(); + for (const std::pair& size : THUMBNAIL_SIZE_SLA) + { + this->thumbnail_data.push_back(ThumbnailData()); + generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, true, false); + } + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR // The print is valid and it can be started. if (this->background_process.start()) { this->statusbar()->set_cancel_callback([this]() { @@ -3116,88 +3166,110 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { - Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk"))); + Plater::TakeSnapshot snapshot(q, _(L("Reload from disk"))); - auto& selection = get_selection(); - const auto obj_orig_idx = selection.get_object_idx(); - if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; } - int instance_idx = selection.get_instance_idx(); + const Selection& selection = get_selection(); - auto *object_orig = model.objects[obj_orig_idx]; - std::vector input_paths(1, object_orig->input_file); - - // disable render to avoid to show intermediate states - view3D->get_canvas3d()->enable_render(false); - - // disable update of objects list while loading to avoid to show intermediate states - m_update_objects_list_on_loading = false; - - const auto new_idxs = load_files(input_paths, true, false); - if (new_idxs.empty()) - { - // error while loading - view3D->get_canvas3d()->enable_render(true); + if (selection.is_wipe_tower()) return; - } - for (const auto idx : new_idxs) + // struct to hold selected ModelVolumes by their indices + struct SelectedVolume { - ModelObject *object = model.objects[idx]; - object->config.apply(object_orig->config); + int object_idx; + int volume_idx; - object->clear_instances(); - for (const ModelInstance *instance : object_orig->instances) + // operators needed by std::algorithms + bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); } + bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); } + }; + std::vector selected_volumes; + + // collects selected ModelVolumes + const std::set& selected_volumes_idxs = selection.get_volume_idxs(); + for (unsigned int idx : selected_volumes_idxs) + { + const GLVolume* v = selection.get_volume(idx); + int o_idx = v->object_idx(); + int v_idx = v->volume_idx(); + selected_volumes.push_back({ o_idx, v_idx }); + } + std::sort(selected_volumes.begin(), selected_volumes.end()); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); + + // collects paths of files to load + std::vector input_paths; + for (const SelectedVolume& v : selected_volumes) + { + const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx]; + if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file)) + input_paths.push_back(volume->source.input_file); + } + std::sort(input_paths.begin(), input_paths.end()); + input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end()); + + // load one file at a time + for (size_t i = 0; i < input_paths.size(); ++i) + { + const auto& path = input_paths[i].string(); + Model new_model; + try { - object->add_instance(*instance); - } - - for (const ModelVolume* v : object_orig->volumes) - { - if (v->is_modifier()) - object->add_volume(*v); - } - - Vec3d offset = object_orig->origin_translation - object->origin_translation; - - if (object->volumes.size() == object_orig->volumes.size()) - { - for (size_t i = 0; i < object->volumes.size(); i++) + new_model = Model::read_from_file(path, nullptr, true, false); + for (ModelObject* model_object : new_model.objects) { - object->volumes[i]->config.apply(object_orig->volumes[i]->config); - object->volumes[i]->translate(offset); + model_object->center_around_origin(); + model_object->ensure_on_bed(); } } + catch (std::exception&) + { + // error while loading + view3D->get_canvas3d()->enable_render(true); + return; + } - // XXX: Restore more: layer_height_ranges, layer_height_profile (?) + // update the selected volumes whose source is the current file + for (const SelectedVolume& old_v : selected_volumes) + { + ModelObject* old_model_object = model.objects[old_v.object_idx]; + ModelVolume* old_volume = old_model_object->volumes[old_v.volume_idx]; + int new_volume_idx = old_volume->source.volume_idx; + int new_object_idx = old_volume->source.object_idx; + + if (old_volume->source.input_file == path) + { + if (new_object_idx < (int)new_model.objects.size()) + { + ModelObject* new_model_object = new_model.objects[new_object_idx]; + if (new_volume_idx < (int)new_model_object->volumes.size()) + { + old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); + ModelVolume* new_volume = old_model_object->volumes.back(); + new_volume->set_new_unique_id(); + new_volume->config.apply(old_volume->config); + new_volume->set_type(old_volume->type()); + new_volume->set_material_id(old_volume->material_id()); + new_volume->set_transformation(old_volume->get_transformation()); + new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back()); + old_model_object->delete_volume(old_model_object->volumes.size() - 1); + } + } + } + } } - // re-enable update of objects list - m_update_objects_list_on_loading = true; + model.adjust_min_z(); - // puts the new objects into the list - for (const auto idx : new_idxs) - { - wxGetApp().obj_list()->add_object_to_list(idx); - } - - remove(obj_orig_idx); + // update 3D scene + update(); // new GLVolumes have been created at this point, so update their printable state for (size_t i = 0; i < model.objects.size(); ++i) { view3D->get_canvas3d()->update_instance_printable_state_for_object(i); } - - // re-enable render - view3D->get_canvas3d()->enable_render(true); - - // the previous call to remove() clears the selection - // select newly added objects - selection.clear(); - for (const auto idx : new_idxs) - { - selection.add_instance((unsigned int)idx - 1, instance_idx, false); - } } void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) @@ -3357,6 +3429,23 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) } else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); + + // uncomment the following lines if you want to render into the thumbnail also supports and pad for SLA printer +/* +#if ENABLE_THUMBNAIL_GENERATOR + // update thumbnail data + // for ptSLA generate the thumbnail after supports and pad have been calculated to have them rendered + if ((this->printer_technology == ptSLA) && (evt.status.percent == -3)) + { + this->thumbnail_data.clear(); + for (const std::pair& size : THUMBNAIL_SIZE_SLA) + { + this->thumbnail_data.push_back(ThumbnailData()); + generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, false, false); + } + } +#endif // ENABLE_THUMBNAIL_GENERATOR +*/ } } @@ -3582,6 +3671,13 @@ bool Plater::priv::init_object_menu() return true; } +#if ENABLE_THUMBNAIL_GENERATOR +void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background) +{ + view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, transparent_background); +} +#endif // ENABLE_THUMBNAIL_GENERATOR + void Plater::priv::msw_rescale_object_menu() { for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu }) @@ -3622,6 +3718,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), [this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q); + append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")), + [this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q); + sidebar->obj_list()->append_menu_item_export_stl(menu); } else { @@ -3648,8 +3747,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); menu->AppendSeparator(); - append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), - [this](wxCommandEvent&) { reload_from_disk(); }); + append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected object from disk")), + [this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q); append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")), [this](wxCommandEvent&) { q->export_stl(false, true); }); @@ -3804,6 +3903,48 @@ bool Plater::priv::can_mirror() const return get_selection().is_from_single_instance(); } +bool Plater::priv::can_reload_from_disk() const +{ + // struct to hold selected ModelVolumes by their indices + struct SelectedVolume + { + int object_idx; + int volume_idx; + + // operators needed by std::algorithms + bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); } + bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); } + }; + std::vector selected_volumes; + + const Selection& selection = get_selection(); + + // collects selected ModelVolumes + const std::set& selected_volumes_idxs = selection.get_volume_idxs(); + for (unsigned int idx : selected_volumes_idxs) + { + const GLVolume* v = selection.get_volume(idx); + int v_idx = v->volume_idx(); + if (v_idx >= 0) + selected_volumes.push_back({ v->object_idx(), v_idx }); + } + std::sort(selected_volumes.begin(), selected_volumes.end()); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); + + // collects paths of files to load + std::vector paths; + for (const SelectedVolume& v : selected_volumes) + { + const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx]; + if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file)) + paths.push_back(volume->source.input_file); + } + std::sort(paths.begin(), paths.end()); + paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); + + return !paths.empty(); +} + void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) { bool new_shape = bed.set_shape(shape, custom_texture, custom_model); @@ -4485,10 +4626,10 @@ void Plater::export_stl(bool extended, bool selection_only) bool is_left_handed = object->is_left_handed(); TriangleMesh pad_mesh; - bool has_pad_mesh = object->has_mesh(slaposBasePool); + bool has_pad_mesh = object->has_mesh(slaposPad); if (has_pad_mesh) { - pad_mesh = object->get_mesh(slaposBasePool); + pad_mesh = object->get_mesh(slaposPad); pad_mesh.transform(mesh_trafo_inv); } @@ -4575,7 +4716,13 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); wxBusyCursor wait; +#if ENABLE_THUMBNAIL_GENERATOR + ThumbnailData thumbnail_data; + p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true); + if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, &thumbnail_data)) { +#else if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { +#endif // ENABLE_THUMBNAIL_GENERATOR // Success p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path)); p->set_project_filename(path); @@ -4586,6 +4733,11 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) } } +void Plater::reload_from_disk() +{ + p->reload_from_disk(); +} + bool Plater::has_toolpaths_to_export() const { return p->preview->get_canvas3d()->has_toolpaths_to_export(); @@ -4664,7 +4816,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error // Otherwise calculate everything, but start with the provided object. if (!this->p->background_processing_enabled()) { task.single_model_instance_only = true; - task.to_object_step = slaposBasePool; + task.to_object_step = slaposPad; } this->p->background_process.set_task(task); // and let the background processing start. @@ -4734,7 +4886,7 @@ bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** o const std::vector& ss_stack = p->undo_redo_stack().snapshots(); const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx); - if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) { *out_text = ss_stack[idx_in_ss_stack].name.c_str(); return true; } @@ -4747,7 +4899,7 @@ void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& ou const std::vector& ss_stack = p->undo_redo_stack().snapshots(); const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0); - if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) { out_text = ss_stack[idx_in_ss_stack].name; return; } @@ -4808,6 +4960,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); p->config->option(opt_key)->values = filament_colors; + p->sidebar->obj_list()->update_extruder_colors(); continue; } } @@ -4833,6 +4986,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) else if(opt_key == "extruder_colour") { update_scheduled = true; p->preview->set_number_extruders(p->config->option(opt_key)->values.size()); + p->sidebar->obj_list()->update_extruder_colors(); } else if(opt_key == "max_print_height") { update_scheduled = true; } @@ -4860,6 +5014,36 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->p->schedule_background_process(); } +void Plater::force_filament_colors_update() +{ + bool update_scheduled = false; + DynamicPrintConfig* config = p->config; + const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; + if (filament_presets.size() > 1 && + p->config->option("filament_colour")->values.size() == filament_presets.size()) + { + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + std::vector filament_colors; + filament_colors.reserve(filament_presets.size()); + + for (const std::string& filament_preset : filament_presets) + filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); + + if (config->option("filament_colour")->values != filament_colors) { + config->option("filament_colour")->values = filament_colors; + update_scheduled = true; + } + } + + if (update_scheduled) { + update(); + p->sidebar->obj_list()->update_extruder_colors(); + } + + if (p->main_frame->is_loaded()) + this->p->schedule_background_process(); +} + void Plater::on_activate() { #ifdef __linux__ @@ -4881,6 +5065,22 @@ const DynamicPrintConfig* Plater::get_plater_config() const return p->config; } +std::vector Plater::get_extruder_colors_from_plater_config() const +{ + const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; + std::vector extruder_colors; + if (!config->has("extruder_colour")) // in case of a SLA print + return extruder_colors; + + extruder_colors = (config->option("extruder_colour"))->values; + const std::vector& filament_colours = (p->config->option("filament_colour"))->values; + for (size_t i = 0; i < extruder_colors.size(); ++i) + if (extruder_colors[i] == "" && i < filament_colours.size()) + extruder_colors[i] = filament_colours[i]; + + return extruder_colors; +} + wxString Plater::get_project_filename(const wxString& extension) const { return p->get_project_filename(extension); @@ -5083,6 +5283,7 @@ bool Plater::can_copy_to_clipboard() const bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); } bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } +bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index dddad9d6c9..9c78e2ee5f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -56,8 +56,12 @@ public: ScalableButton* edit_btn { nullptr }; enum LabelItemType { - LABEL_ITEM_MARKER = 0x4d, - LABEL_ITEM_CONFIG_WIZARD = 0x4e + LABEL_ITEM_MARKER = 0xffffff01, + LABEL_ITEM_WIZARD_PRINTERS, + LABEL_ITEM_WIZARD_FILAMENTS, + LABEL_ITEM_WIZARD_MATERIALS, + + LABEL_ITEM_MAX, }; void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); @@ -184,6 +188,7 @@ public: void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); + void reload_from_disk(); bool has_toolpaths_to_export() const; void export_toolpaths_to_obj() const; void reslice(); @@ -212,9 +217,11 @@ public: void on_extruders_change(size_t extruders_count); void on_config_change(const DynamicPrintConfig &config); + void force_filament_colors_update(); // On activating the parent window. void on_activate(); const DynamicPrintConfig* get_plater_config() const; + std::vector get_extruder_colors_from_plater_config() const; void update_object_menu(); @@ -248,6 +255,7 @@ public: bool can_copy_to_clipboard() const; bool can_undo() const; bool can_redo() const; + bool can_reload_from_disk() const; void msw_rescale(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index f6164bc45f..ab0d476af7 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -99,6 +99,9 @@ static const std::unordered_map pre_family_model_map { VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all) { static const std::string printer_model_key = "printer_model:"; + static const std::string filaments_section = "default_filaments"; + static const std::string materials_section = "default_sla_materials"; + const std::string id = path.stem().string(); if (! boost::filesystem::exists(path)) { @@ -107,6 +110,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem VendorProfile res(id); + // Helper to get compulsory fields auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator { auto res = tree.find(key); @@ -116,6 +120,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem return res; }; + // Load the header const auto &vendor_section = get_or_throw(tree, "vendor")->second; res.name = get_or_throw(vendor_section, "name")->second.data(); @@ -127,6 +132,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem res.config_version = std::move(*config_version); } + // Load URLs const auto config_update_url = vendor_section.find("config_update_url"); if (config_update_url != vendor_section.not_found()) { res.config_update_url = config_update_url->second.data(); @@ -141,6 +147,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem return res; } + // Load printer models for (auto §ion : tree) { if (boost::starts_with(section.first, printer_model_key)) { VendorProfile::PrinterModel model; @@ -182,6 +189,24 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem } } + // Load filaments and sla materials to be installed by default + const auto filaments = tree.find(filaments_section); + if (filaments != tree.not_found()) { + for (auto &pair : filaments->second) { + if (pair.second.data() == "1") { + res.default_filaments.insert(pair.first); + } + } + } + const auto materials = tree.find(materials_section); + if (materials != tree.not_found()) { + for (auto &pair : materials->second) { + if (pair.second.data() == "1") { + res.default_sla_materials.insert(pair.first); + } + } + } + return res; } @@ -220,27 +245,13 @@ std::string Preset::remove_suffix_modified(const std::string &name) name; } -void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders) -{ - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : Preset::nozzle_options()) { - if (key == "default_filament_profile") - continue; - auto *opt = config.option(key, false); - assert(opt != nullptr); - assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) - static_cast(opt)->resize(num_extruders, defaults.option(key)); - } -} - // Update new extruder fields at the printer profile. void Preset::normalize(DynamicPrintConfig &config) { auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); if (nozzle_diameter != nullptr) // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. - set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); + config.set_num_extruders((unsigned int)nozzle_diameter->values.size()); if (config.option("filament_diameter") != nullptr) { // This config contains single or multiple filament presets. // Ensure that the filament preset vector options contain the correct number of values. @@ -351,10 +362,17 @@ bool Preset::update_compatible(const Preset &active_printer, const DynamicPrintC void Preset::set_visible_from_appconfig(const AppConfig &app_config) { if (vendor == nullptr) { return; } - const std::string &model = config.opt_string("printer_model"); - const std::string &variant = config.opt_string("printer_variant"); - if (model.empty() || variant.empty()) { return; } - is_visible = app_config.get_variant(vendor->id, model, variant); + + if (type == TYPE_PRINTER) { + const std::string &model = config.opt_string("printer_model"); + const std::string &variant = config.opt_string("printer_variant"); + if (model.empty() || variant.empty()) { return; } + is_visible = app_config.get_variant(vendor->id, model, variant); + } else if (type == TYPE_FILAMENT) { + is_visible = app_config.has("filaments", name); + } else if (type == TYPE_SLA_MATERIAL) { + is_visible = app_config.has("sla_materials", name); + } } const std::vector& Preset::print_options() @@ -404,7 +422,7 @@ const std::vector& Preset::filament_options() "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", // Profile compatibility - "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" + "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } @@ -437,15 +455,7 @@ const std::vector& Preset::printer_options() // of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { - // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings - static std::vector s_opts { - "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", - "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", - "default_filament_profile" - }; - return s_opts; + return print_config_def.extruder_option_keys(); } const std::vector& Preset::sla_print_options() @@ -476,11 +486,13 @@ const std::vector& Preset::sla_print_options() "pad_enable", "pad_wall_thickness", "pad_wall_height", + "pad_brim_size", "pad_max_merge_distance", // "pad_edge_radius", "pad_wall_slope", "pad_object_gap", "pad_around_object", + "pad_around_object_everywhere", "pad_object_connector_stride", "pad_object_connector_width", "pad_object_connector_penetration", @@ -499,6 +511,7 @@ const std::vector& Preset::sla_material_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { + "material_type", "initial_layer_height", "bottle_cost", "bottle_volume", @@ -508,6 +521,7 @@ const std::vector& Preset::sla_material_options() "initial_exposure_time", "material_correction", "material_notes", + "material_vendor", "default_sla_material_profile", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" @@ -823,6 +837,21 @@ bool PresetCollection::delete_current_preset() return true; } +bool PresetCollection::delete_preset(const std::string& name) +{ + auto it = this->find_preset_internal(name); + + const Preset& preset = *it; + if (preset.is_default) + return false; + if (!preset.is_external && !preset.is_system) { + // Erase the preset file. + boost::nowide::remove(preset.file.c_str()); + } + m_presets.erase(it); + return true; +} + void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) { // XXX: See note in PresetBundle::load_compatible_bitmaps() @@ -1041,7 +1070,9 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); bmp = m_bitmap_cache->insert(bitmap_key, bmps); } - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD); + ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_PRINTERS); + } else if (m_type == Preset::TYPE_SLA_MATERIAL) { + ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS); } ui->SetSelection(selected_preset_item); @@ -1287,7 +1318,7 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name) } // Merge one vendor's presets with the other vendor's presets, report duplicates. -std::vector PresetCollection::merge_presets(PresetCollection &&other, const std::set &new_vendors) +std::vector PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors) { std::vector duplicates; for (Preset &preset : other.m_presets) { @@ -1298,9 +1329,9 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe if (it == m_presets.end() || it->name != preset.name) { if (preset.vendor != nullptr) { // Re-assign a pointer to the vendor structure in the new PresetBundle. - auto it = new_vendors.find(*preset.vendor); + auto it = new_vendors.find(preset.vendor->id); assert(it != new_vendors.end()); - preset.vendor = &(*it); + preset.vendor = &it->second; } this->m_presets.emplace(it, std::move(preset)); } else diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index e1efdc1ef0..e2e4baa88c 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -2,6 +2,8 @@ #define slic3r_Preset_hpp_ #include +#include +#include #include #include @@ -71,9 +73,14 @@ public: }; std::vector models; + std::set default_filaments; + std::set default_sla_materials; + VendorProfile() {} VendorProfile(std::string id) : id(std::move(id)) {} + // Load VendorProfile from an ini file. + // If `load_all` is false, only the header with basic info (name, version, URLs) is loaded. static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true); static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true); @@ -84,6 +91,12 @@ public: bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; } }; +// Note: it is imporant that map is used here rather than unordered_map, +// because we need iterators to not be invalidated, +// because Preset and the ConfigWizard hold pointers to VendorProfiles. +// XXX: maybe set is enough (cf. changes in Wizard) +typedef std::map VendorMap; + class Preset { public: @@ -189,7 +202,7 @@ public: void set_visible_from_appconfig(const AppConfig &app_config); // Resize the extruder specific fields, initialize them with the content of the 1st extruder. - void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); } + void set_num_extruders(unsigned int n) { this->config.set_num_extruders(n); } // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const Preset &other) const { return this->name < other.name; } @@ -214,8 +227,6 @@ public: protected: friend class PresetCollection; friend class PresetBundle; - // Resize the extruder specific vectors () - static void set_num_extruders(DynamicPrintConfig &config, unsigned int n); static std::string remove_suffix_modified(const std::string &name); }; @@ -276,6 +287,9 @@ public: // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. bool delete_current_preset(); + // Delete the current preset, activate the first visible preset. + // returns true if the preset was deleted successfully. + bool delete_preset(const std::string& name); // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. void load_bitmap_default(wxWindow *window, const std::string &file_name); @@ -430,7 +444,7 @@ protected: bool select_preset_by_name_strict(const std::string &name); // Merge one vendor's presets with the other vendor's presets, report duplicates. - std::vector merge_presets(PresetCollection &&other, const std::set &new_vendors); + std::vector merge_presets(PresetCollection &&other, const VendorMap &new_vendors); private: PresetCollection(); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 5785fd8507..92db623f02 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,8 @@ static std::vector s_project_options { "wiping_volumes_matrix" }; +const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch"; + PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())), filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), @@ -194,7 +197,7 @@ void PresetBundle::setup_directories() } } -void PresetBundle::load_presets(const AppConfig &config, const std::string &preferred_model_id) +void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id) { // First load the vendor specific system presets. std::string errors_cummulative = this->load_system_presets(); @@ -325,13 +328,71 @@ void PresetBundle::load_installed_printers(const AppConfig &config) } } +void PresetBundle::load_installed_filaments(AppConfig &config) +{ + if (! config.has_section(AppConfig::SECTION_FILAMENTS)) { + std::unordered_set comp_filaments; + + for (const Preset &printer : printers) { + if (! printer.is_visible || printer.printer_technology() != ptFFF) { + continue; + } + + for (const Preset &filament : filaments) { + if (filament.is_compatible_with_printer(printer)) { + comp_filaments.insert(&filament); + } + } + } + + for (const auto &filament: comp_filaments) { + config.set(AppConfig::SECTION_FILAMENTS, filament->name, "1"); + } + } + + for (auto &preset : filaments) { + preset.set_visible_from_appconfig(config); + } +} + +void PresetBundle::load_installed_sla_materials(AppConfig &config) +{ + if (! config.has_section(AppConfig::SECTION_MATERIALS)) { + std::unordered_set comp_sla_materials; + + for (const Preset &printer : printers) { + if (! printer.is_visible || printer.printer_technology() != ptSLA) { + continue; + } + + for (const Preset &material : sla_materials) { + if (material.is_compatible_with_printer(printer)) { + comp_sla_materials.insert(&material); + } + } + } + + for (const auto &material: comp_sla_materials) { + config.set(AppConfig::SECTION_MATERIALS, material->name, "1"); + } + } + + for (auto &preset : sla_materials) { + preset.set_visible_from_appconfig(config); + } +} + // Load selections (current print, current filaments, current printer) from config.ini // This is done on application start up or after updates are applied. -void PresetBundle::load_selections(const AppConfig &config, const std::string &preferred_model_id) +void PresetBundle::load_selections(AppConfig &config, const std::string &preferred_model_id) { // Update visibility of presets based on application vendor / model / variant configuration. this->load_installed_printers(config); + // Update visibility of filament and sla material presets + this->load_installed_filaments(config); + this->load_installed_sla_materials(config); + // Parse the initial print / filament / printer profile names. std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); std::string initial_sla_print_profile_name = remove_ini_suffix(config.get("presets", "sla_print")); @@ -1032,9 +1093,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla auto vp = VendorProfile::from_ini(tree, path); if (vp.num_variants() == 0) return 0; - vendor_profile = &(*this->vendors.insert(vp).first); + vendor_profile = &this->vendors.insert({vp.id, vp}).first->second; } - + if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) { return 0; } @@ -1572,6 +1633,9 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr selected_preset_item = ui->GetCount() - 1; } } + + ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS); + ui->SetSelection(selected_preset_item); ui->SetToolTip(ui->GetString(selected_preset_item)); ui->check_selection(); diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index f351f66ac3..b1010e07b0 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -4,7 +4,9 @@ #include "AppConfig.hpp" #include "Preset.hpp" +#include #include +#include #include class wxWindow; @@ -31,7 +33,7 @@ public: // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. - void load_presets(const AppConfig &config, const std::string &preferred_model_id = ""); + void load_presets(AppConfig &config, const std::string &preferred_model_id = ""); // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); @@ -52,7 +54,8 @@ public: // There will be an entry for each system profile loaded, // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. - std::set vendors; + // std::set vendors; + VendorMap vendors; struct ObsoletePresets { std::vector prints; @@ -131,19 +134,25 @@ public: void load_default_preset_bitmaps(wxWindow *window); + // Set the is_visible flag for printer vendors, printer models and printer variants + // based on the user configuration. + // If the "vendor" section is missing, enable all models and variants of the particular vendor. + void load_installed_printers(const AppConfig &config); + + static const char *PRUSA_BUNDLE; private: std::string load_system_presets(); // Merge one vendor's presets with the other vendor's presets, report duplicates. std::vector merge_presets(PresetBundle &&other); - // Set the "enabled" flag for printer vendors, printer models and printer variants - // based on the user configuration. - // If the "vendor" section is missing, enable all models and variants of the particular vendor. - void load_installed_printers(const AppConfig &config); + // Set the is_visible flag for filaments and sla materials, + // apply defaults based on enabled printers when no filaments/materials are installed. + void load_installed_filaments(AppConfig &config); + void load_installed_sla_materials(AppConfig &config); // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. - void load_selections(const AppConfig &config, const std::string &preferred_model_id = ""); + void load_selections(AppConfig &config, const std::string &preferred_model_id = ""); // Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path. // and the external config is just referenced, not stored into user profile directory. diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 13d4a73602..13be482896 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -472,7 +472,7 @@ void Selection::volumes_changed(const std::vector &map_volume_old_to_new for (unsigned int idx : m_list) if (map_volume_old_to_new[idx] != size_t(-1)) { unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; - assert((*m_volumes)[new_idx]->selected); + (*m_volumes)[new_idx]->selected = true; list_new.insert(new_idx); } m_list = std::move(list_new); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 25604080e8..5063c2fbc7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -227,9 +227,9 @@ void Tab::create_preset_tab() m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, + //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, //! but the OSX version derived from wxOwnerDrawnCombo, instead of: - //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); + //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); //! we doing next: int selected_item = m_presets_choice->GetSelection(); if (m_selected_preset_item == size_t(selected_item) && !m_presets->current_is_dirty()) @@ -241,7 +241,7 @@ void Tab::create_preset_tab() selected_string == "------- User presets -------"*/) { m_presets_choice->SetSelection(m_selected_preset_item); if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) - wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER); }); return; } m_selected_preset_item = selected_item; @@ -1808,7 +1808,10 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("single_extruder_multi_material"); optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); + // optgroup->get_value() return int for def.type == coInt, + // Thus, there should be boost::any_cast ! + // Otherwise, boost::any_cast causes an "unhandled unknown exception" + size_t extruders_count = size_t(boost::any_cast(optgroup->get_value("extruders_count"))); wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { extruders_count_changed(extruders_count); @@ -3016,6 +3019,18 @@ void Tab::save_preset(std::string name /*= ""*/) show_error(this, _(L("Cannot overwrite an external profile."))); return; } + if (existing && name != preset.name) + { + wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exist."))) % name).str()); + msg_text += "\n" + _(L("Replace?")); + wxMessageDialog dialog(nullptr, msg_text, _(L("Warning")), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return; + + // Remove the preset from the list. + m_presets->delete_preset(name); + } } // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini @@ -3032,6 +3047,12 @@ void Tab::save_preset(std::string name /*= ""*/) if (m_type == Preset::TYPE_PRINTER) static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; update_changed_ui(); + + /* If filament preset is saved for multi-material printer preset, + * there are cases when filament comboboxs are updated for old (non-modified) colors, + * but in full_config a filament_colors option aren't.*/ + if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + wxGetApp().plater()->force_filament_colors_update(); } // Called for a currently selected preset. @@ -3563,12 +3584,14 @@ void TabSLAPrint::build() optgroup->append_single_option_line("pad_enable"); optgroup->append_single_option_line("pad_wall_thickness"); optgroup->append_single_option_line("pad_wall_height"); + optgroup->append_single_option_line("pad_brim_size"); optgroup->append_single_option_line("pad_max_merge_distance"); // TODO: Disabling this parameter for the beta release // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); optgroup->append_single_option_line("pad_around_object"); + optgroup->append_single_option_line("pad_around_object_everywhere"); optgroup->append_single_option_line("pad_object_gap"); optgroup->append_single_option_line("pad_object_connector_stride"); optgroup->append_single_option_line("pad_object_connector_width"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 59406b6e97..eb47fd2083 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Model.hpp" #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "libslic3r/GCode/PreviewData.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" +#include "PresetBundle.hpp" #include "../Utils/MacDarkMode.hpp" using Slic3r::GUI::from_u8; @@ -445,6 +447,52 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, return *bmp; } + +Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr; +/*static*/ std::vector get_extruder_color_icons() +{ + // Create the bitmap with color bars. + std::vector bmps; + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + + if (colors.empty()) + return bmps; + + unsigned char rgb[3]; + + /* It's supposed that standard size of an icon is 36px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const double em = Slic3r::GUI::wxGetApp().em_unit(); + const int icon_width = lround(3.2 * em); + const int icon_height = lround(1.6 * em); + + for (const std::string& color : colors) + { + wxBitmap* bitmap = m_bitmap_cache->find(color); + if (bitmap == nullptr) { + // Paint the color icon. + Slic3r::PresetBundle::parse_color(color, rgb); + bitmap = m_bitmap_cache->insert(color, m_bitmap_cache->mksolid(icon_width, icon_height, rgb)); + } + bmps.emplace_back(bitmap); + } + + return bmps; +} + + +static wxBitmap get_extruder_color_icon(size_t extruder_idx) +{ + // Create the bitmap with color bars. + std::vector bmps = get_extruder_color_icons(); + if (bmps.empty()) + return wxNullBitmap; + + return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx]; +} + // ***************************************************************************** // ---------------------------------------------------------------------------- // ObjectDataViewModelNode @@ -477,7 +525,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_idx = parent->GetChildCount(); m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); - set_action_icon(); + set_action_and_extruder_icons(); } else if (type == itLayerRoot) { @@ -512,7 +560,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; m_bmp = create_scaled_bitmap(nullptr, LAYER_ICON); // FIXME: pass window ptr - set_action_icon(); + set_action_and_extruder_icons(); init_container(); } @@ -525,11 +573,19 @@ bool ObjectDataViewModelNode::valid() } #endif /* NDEBUG */ -void ObjectDataViewModelNode::set_action_icon() +void ObjectDataViewModelNode::set_action_and_extruder_icons() { m_action_icon_name = m_type & itObject ? "advanced_plus" : m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr + + if (m_type & itInstance) + return; // don't set colored bitmap for Instance + + // set extruder bitmap + int extruder_idx = atoi(m_extruder.c_str()); + if (extruder_idx > 0) --extruder_idx; + m_extruder_bmp = get_extruder_color_icon(extruder_idx); } void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) @@ -539,7 +595,6 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) create_scaled_bitmap(nullptr, m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); } -Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; void ObjectDataViewModelNode::update_settings_digest_bitmaps() { m_bmp = m_empty_bmp; @@ -605,8 +660,10 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) m_name = data.GetText(); return true; } case colExtruder: { - const wxString & val = variant.GetString(); - m_extruder = val == "0" ? _(L("default")) : val; + DataViewBitmapText data; + data << variant; + m_extruder_bmp = data.GetBitmap(); + m_extruder = data.GetText() == "0" ? _(L("default")) : data.GetText(); return true; } case colEditing: m_action_icon << variant; @@ -963,7 +1020,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) node_parent->GetChildren().Remove(node); if (id > 0) { - if(id == node_parent->GetChildCount()) id--; + if (size_t(id) == node_parent->GetChildCount()) id--; ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); } @@ -1379,6 +1436,51 @@ t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewIt return node->GetLayerRange(); } +bool ObjectDataViewModel::UpdateColumValues(unsigned col) +{ + switch (col) + { + case colPrint: + case colName: + case colEditing: + return true; + case colExtruder: + { + wxDataViewItemArray items; + GetAllChildren(wxDataViewItem(nullptr), items); + + if (items.IsEmpty()) return false; + + for (auto item : items) + UpdateExtruderBitmap(item); + + return true; + } + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + + +void ObjectDataViewModel::UpdateExtruderBitmap(wxDataViewItem item) +{ + wxString extruder = GetExtruder(item); + if (extruder.IsEmpty()) + return; + + // set extruder bitmap + int extruder_idx = atoi(extruder.c_str()); + if (extruder_idx > 0) --extruder_idx; + + const DataViewBitmapText extruder_val(extruder, get_extruder_color_icon(extruder_idx)); + + wxVariant value; + value << extruder_val; + + SetValue(value, item, colExtruder); +} + void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) { wxASSERT(item.IsOk()); @@ -1475,6 +1577,24 @@ wxBitmap& ObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const return node->m_bmp; } +wxString ObjectDataViewModel::GetExtruder(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_extruder; +} + +int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return 0; + + return atoi(node->m_extruder.c_str()); +} + void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); @@ -1489,7 +1609,7 @@ void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &ite variant << DataViewBitmapText(node->m_name, node->m_bmp); break; case colExtruder: - variant = node->m_extruder; + variant << DataViewBitmapText(node->m_extruder, node->m_extruder_bmp); break; case colEditing: variant << node->m_action_icon; @@ -1515,6 +1635,22 @@ bool ObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, return m_objects[item_idx]->SetValue(variant, col); } +void ObjectDataViewModel::SetExtruder(const wxString& extruder, wxDataViewItem item) +{ + DataViewBitmapText extruder_val; + extruder_val.SetText(extruder); + + // set extruder bitmap + int extruder_idx = atoi(extruder.c_str()); + if (extruder_idx > 0) --extruder_idx; + extruder_val.SetBitmap(get_extruder_color_icon(extruder_idx)); + + wxVariant value; + value << extruder_val; + + SetValue(value, item, colExtruder); +} + wxDataViewItem ObjectDataViewModel::ReorganizeChildren( const int current_volume_id, const int new_volume_id, const wxDataViewItem &parent) @@ -1840,7 +1976,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo } //----------------------------------------------------------------------------- -// PrusaDataViewBitmapText +// DataViewBitmapText //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) @@ -1966,6 +2102,109 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value return true; } +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +bool BitmapChoiceRenderer::SetValue(const wxVariant& value) +{ + m_value << value; + return true; +} + +bool BitmapChoiceRenderer::GetValue(wxVariant& value) const +{ + value << m_value; + return true; +} + +bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); + xoffset = icon.GetWidth() + 4; + } + + if (rect.height==0) + rect.height= icon.GetHeight(); + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapChoiceRenderer::GetSize() const +{ + wxSize sz = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + sz.x += m_value.GetBitmap().GetWidth() + 4; + + return sz; +} + + +wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); + ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); + + if (!(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itLayer | itObject))) + return nullptr; + + std::vector icons = get_extruder_color_icons(); + if (icons.empty()) + return nullptr; + + DataViewBitmapText data; + data << value; + + auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, + labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), + 0, nullptr , wxCB_READONLY); + + int i=0; + for (wxBitmap* bmp : icons) { + if (i==0) { + c_editor->Append(_(L("default")), *bmp); + ++i; + } + + c_editor->Append(wxString::Format("%d", i), *bmp); + ++i; + } + c_editor->SetSelection(atoi(data.GetText().c_str())); + + // to avoid event propagation to other sidebar items + c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + evt.StopPropagation(); + // FinishEditing grabs new selection and triggers config update. We better call + // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. + this->FinishEditing(); + }); + + return c_editor; +} + +bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; + int selection = c->GetSelection(); + if (selection < 0) + return false; + + DataViewBitmapText bmpText; + + bmpText.SetText(c->GetString(selection)); + bmpText.SetBitmap(c->GetItemBitmap(selection)); + + value << bmpText; + return true; +} + // ---------------------------------------------------------------------------- // DoubleSlider // ---------------------------------------------------------------------------- @@ -2987,6 +3226,8 @@ void LockButton::msw_rescale() m_bmp_lock_closed_f.msw_rescale(); m_bmp_lock_open.msw_rescale(); m_bmp_lock_open_f.msw_rescale(); + + update_button_bitmaps(); } void LockButton::update_button_bitmaps() diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 54d1bf7cbf..d4d5e79986 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -55,6 +55,8 @@ int em_unit(wxWindow* win); wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); +std::vector get_extruder_color_icons(); + class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { static const unsigned int DefaultWidth; @@ -209,6 +211,7 @@ class ObjectDataViewModelNode int m_idx = -1; bool m_container = false; wxString m_extruder = "default"; + wxBitmap m_extruder_bmp; wxBitmap m_action_icon; PrintIndicator m_printable {piUndef}; wxBitmap m_printable_icon; @@ -224,7 +227,7 @@ public: m_type(itObject), m_extruder(extruder) { - set_action_icon(); + set_action_and_extruder_icons(); init_container(); } @@ -240,7 +243,7 @@ public: m_extruder (extruder) { m_bmp = bmp; - set_action_icon(); + set_action_and_extruder_icons(); init_container(); } @@ -356,7 +359,7 @@ public: } // Set action icons for node - void set_action_icon(); + void set_action_and_extruder_icons(); // Set printable icon for node void set_printable_icon(PrintIndicator printable); @@ -438,6 +441,8 @@ public: wxString GetName(const wxDataViewItem &item) const; wxBitmap& GetBitmap(const wxDataViewItem &item) const; + wxString GetExtruder(const wxDataViewItem &item) const; + int GetExtruderNumber(const wxDataViewItem &item) const; // helper methods to change the model @@ -454,6 +459,8 @@ public: const int item_idx, unsigned int col); + void SetExtruder(const wxString& extruder, wxDataViewItem item); + // For parent move child from cur_volume_id place to new_volume_id // Remaining items will moved up/down accordingly wxDataViewItem ReorganizeChildren( const int cur_volume_id, @@ -504,6 +511,9 @@ public: void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; + bool UpdateColumValues(unsigned col); + void UpdateExtruderBitmap(wxDataViewItem item); + private: wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); @@ -563,6 +573,40 @@ private: }; +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +class BitmapChoiceRenderer : public wxDataViewCustomRenderer +{ +public: + BitmapChoiceRenderer(wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + ,int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} + + bool SetValue(const wxVariant& value); + bool GetValue(wxVariant& value) const; + + virtual bool Render(wxRect cell, wxDC* dc, int state); + virtual wxSize GetSize() const; + + bool HasEditorCtrl() const override { return true; } + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override; + bool GetValueFromEditorCtrl( wxWindow* ctrl, + wxVariant& value) override; + +private: + DataViewBitmapText m_value; +}; + + // ---------------------------------------------------------------------------- // MyCustomRenderer // ---------------------------------------------------------------------------- diff --git a/src/slic3r/Utils/FlashAir.cpp b/src/slic3r/Utils/FlashAir.cpp new file mode 100644 index 0000000000..3fc913c994 --- /dev/null +++ b/src/slic3r/Utils/FlashAir.cpp @@ -0,0 +1,219 @@ +#include "FlashAir.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "Http.hpp" + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +namespace Slic3r { + +FlashAir::FlashAir(DynamicPrintConfig *config) : + host(config->opt_string("print_host")) +{} + +FlashAir::~FlashAir() {} + +const char* FlashAir::get_name() const { return "FlashAir"; } + +bool FlashAir::test(wxString &msg) const +{ + // Since the request is performed synchronously here, + // it is ok to refer to `msg` from within the closure + + const char *name = get_name(); + + bool res = false; + auto url = make_url("command.cgi", "op", "118"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url; + + auto http = Http::get(std::move(url)); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + res = false; + msg = format_error(body, error, status); + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body; + + res = boost::starts_with(body, "1"); + if (! res) { + msg = _(L("Upload not enabled on FlashAir card.")); + } + }) + .perform_sync(); + + return res; +} + +wxString FlashAir::get_test_ok_msg () const +{ + return _(L("Connection to FlashAir works correctly and upload is enabled.")); +} + +wxString FlashAir::get_test_failed_msg (wxString &msg) const +{ + return wxString::Format("%s: %s", _(L("Could not connect to FlashAir")), msg, _(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))); +} + +bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const +{ + const char *name = get_name(); + + const auto upload_filename = upload_data.upload_path.filename(); + const auto upload_parent_path = upload_data.upload_path.parent_path(); + + wxString test_msg; + if (! test(test_msg)) { + error_fn(std::move(test_msg)); + return false; + } + + bool res = false; + + auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str()); + auto urlUpload = make_url("upload.cgi"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%") + % name + % upload_data.source_path + % urlPrepare + % urlUpload + % upload_filename.string(); + + // set filetime for upload and make card writeprotect to prevent filesystem damage + auto httpPrepare = Http::get(std::move(urlPrepare)); + httpPrepare.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error prepareing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .perform_sync(); + + if(! res ) { + return res; + } + + // start file upload + auto http = Http::post(std::move(urlUpload)); + http.form_add_file("file", upload_data.source_path.string(), upload_filename.string()) + .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_progress([&](Http::Progress progress, bool &cancel) { + prorgess_fn(std::move(progress), cancel); + if (cancel) { + // Upload was canceled + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name; + res = false; + } + }) + .perform_sync(); + + return res; +} + +bool FlashAir::has_auto_discovery() const +{ + return false; +} + +bool FlashAir::can_test() const +{ + return true; +} + +bool FlashAir::can_start_print() const +{ + return false; +} + +std::string FlashAir::timestamp_str() const +{ + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + + const char *name = get_name(); + + unsigned long fattime = ((tm.tm_year - 80) << 25) | + ((tm.tm_mon + 1) << 21) | + (tm.tm_mday << 16) | + (tm.tm_hour << 11) | + (tm.tm_min << 5) | + (tm.tm_sec >> 1); + + return (boost::format("%1$#x") % fattime).str(); +} + +std::string FlashAir::make_url(const std::string &path) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%") % host % path).str(); + } else { + return (boost::format("%1%/%2%") % host % path).str(); + } + } else { + if (host.back() == '/') { + return (boost::format("http://%1%%2%") % host % path).str(); + } else { + return (boost::format("http://%1%/%2%") % host % path).str(); + } + } +} + +std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str(); + } else { + return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str(); + } + } else { + if (host.back() == '/') { + return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str(); + } else { + return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str(); + } + } +} + +} diff --git a/src/slic3r/Utils/FlashAir.hpp b/src/slic3r/Utils/FlashAir.hpp new file mode 100644 index 0000000000..1499eee5d8 --- /dev/null +++ b/src/slic3r/Utils/FlashAir.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_FlashAir_hpp_ +#define slic3r_FlashAir_hpp_ + +#include +#include + +#include "PrintHost.hpp" + + +namespace Slic3r { + + +class DynamicPrintConfig; +class Http; + +class FlashAir : public PrintHost +{ +public: + FlashAir(DynamicPrintConfig *config); + virtual ~FlashAir(); + + virtual const char* get_name() const; + + virtual bool test(wxString &curl_msg) const; + virtual wxString get_test_ok_msg () const; + virtual wxString get_test_failed_msg (wxString &msg) const; + virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const; + virtual bool has_auto_discovery() const; + virtual bool can_test() const; + virtual bool can_start_print() const; + virtual std::string get_host() const { return host; } + +private: + std::string host; + + std::string timestamp_str() const; + std::string make_url(const std::string &path) const; + std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const; +}; + + +} + +#endif diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 5723afca2f..3cebf2f89e 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -153,7 +153,7 @@ struct PresetUpdater::priv bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; void sync_version() const; - void sync_config(const std::set vendors); + void sync_config(const VendorMap vendors); void check_install_indices() const; Updates get_config_updates() const; @@ -266,7 +266,7 @@ void PresetUpdater::priv::sync_version() const // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const std::set vendors) +void PresetUpdater::priv::sync_config(const VendorMap vendors) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; @@ -276,13 +276,13 @@ void PresetUpdater::priv::sync_config(const std::set vendors) for (auto &index : index_db) { if (cancel) { return; } - const auto vendor_it = vendors.find(VendorProfile(index.vendor())); + const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); continue; } - const VendorProfile &vendor = *vendor_it; + const VendorProfile &vendor = vendor_it->second; if (vendor.config_update_url.empty()) { BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name; continue; @@ -574,7 +574,7 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) // Copy the whole vendors data for use in the background thread // Unfortunatelly as of C++11, it needs to be copied again // into the closure (but perhaps the compiler can elide this). - std::set vendors = preset_bundle->vendors; + VendorMap vendors = preset_bundle->vendors; p->thread = std::move(std::thread([this, vendors]() { this->p->prune_tmps(); @@ -643,13 +643,10 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const // (snapshot is taken beforehand) p->perform_updates(std::move(updates)); - GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); - - if (! wizard.run(GUI::wxGetApp().preset_bundle, this)) { + if (! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) { return R_INCOMPAT_EXIT; } - GUI::wxGetApp().load_current_presets(); return R_INCOMPAT_CONFIGURED; } else { BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye..."; @@ -694,8 +691,8 @@ void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size(); for (const auto &bundle : bundles) { - auto path_in_rsrc = p->rsrc_path / bundle; - auto path_in_vendors = p->vendor_path / bundle; + auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); + auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index ab52b23443..59a929ecca 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -14,6 +14,7 @@ #include "libslic3r/Channel.hpp" #include "OctoPrint.hpp" #include "Duet.hpp" +#include "FlashAir.hpp" #include "../GUI/PrintHostDialogs.hpp" namespace fs = boost::filesystem; @@ -43,6 +44,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) switch (host_type) { case htOctoPrint: return new OctoPrint(config); case htDuet: return new Duet(config); + case htFlashAir: return new FlashAir(config); default: return nullptr; } } else { diff --git a/src/slic3r/Utils/Thread.hpp b/src/slic3r/Utils/Thread.hpp new file mode 100644 index 0000000000..e9c76d2aba --- /dev/null +++ b/src/slic3r/Utils/Thread.hpp @@ -0,0 +1,28 @@ +#ifndef THREAD_HPP +#define THREAD_HPP + +#include +#include + +namespace Slic3r { + +template +inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn) +{ + // Duplicating the stack allocation size of Thread Building Block worker + // threads of the thread pool: allocate 4MB on a 64bit system, allocate 2MB + // on a 32bit system by default. + + attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024)); + return boost::thread{attrs, std::forward(fn)}; +} + +template inline boost::thread create_thread(Fn &&fn) +{ + boost::thread::attributes attrs; + return create_thread(attrs, std::forward(fn)); +} + +} + +#endif // THREAD_HPP diff --git a/t/clipper.t b/t/clipper.t deleted file mode 100644 index 3c9838143a..0000000000 --- a/t/clipper.t +++ /dev/null @@ -1,89 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 6; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(sum); -use Slic3r; -use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex diff_pl); - -{ - my $square = [ # ccw - [10, 10], - [20, 10], - [20, 20], - [10, 20], - ]; - my $hole_in_square = [ # cw - [14, 14], - [14, 16], - [16, 16], - [16, 14], - ]; - my $square2 = [ # ccw - [5, 12], - [25, 12], - [25, 18], - [5, 18], - ]; - my $intersection = intersection_ex([ $square, $hole_in_square ], [ $square2 ]); - - is sum(map $_->area, @$intersection), Slic3r::ExPolygon->new( - [ - [20, 18], - [10, 18], - [10, 12], - [20, 12], - ], - [ - [14, 16], - [16, 16], - [16, 14], - [14, 14], - ], - )->area, 'hole is preserved after intersection'; -} - -#========================================================== - -{ - my $contour1 = [ [0,0], [40,0], [40,40], [0,40] ]; # ccw - my $contour2 = [ [10,10], [30,10], [30,30], [10,30] ]; # ccw - my $hole = [ [15,15], [15,25], [25,25], [25,15] ]; # cw - - my $union = union_ex([ $contour1, $contour2, $hole ]); - - is_deeply [ map $_->pp, @$union ], [[ [ [40,40], [0,40], [0,0], [40,0] ] ]], - 'union of two ccw and one cw is a contour with no holes'; - - my $diff = diff_ex([ $contour1, $contour2 ], [ $hole ]); - is sum(map $_->area, @$diff), - Slic3r::ExPolygon->new([ [40,40], [0,40], [0,0], [40,0] ], [ [15,25], [25,25], [25,15], [15,15] ])->area, - 'difference of a cw from two ccw is a contour with one hole'; -} - -#========================================================== - -{ - my $square = Slic3r::Polygon->new_scale( # ccw - [10, 10], - [20, 10], - [20, 20], - [10, 20], - ); - my $square_pl = $square->split_at_first_point; - - my $res = diff_pl([$square_pl], []); - is scalar(@$res), 1, 'no-op diff_pl returns the right number of polylines'; - isa_ok $res->[0], 'Slic3r::Polyline', 'no-op diff_pl result'; - is scalar(@{$res->[0]}), scalar(@$square_pl), 'no-op diff_pl returns the unmodified input polyline'; -} - -__END__ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 11bdc4b3d9..f77b4bd25f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,30 @@ # TODO Add individual tests as executables in separate directories +# add_subirectory() -# add_subirectory() \ No newline at end of file +set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data) +file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR) + +add_library(Catch2 INTERFACE) +list (APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/Catch2) +target_include_directories(Catch2 INTERFACE ${CMAKE_CURRENT_LIST_DIR}) +add_library(Catch2::Catch2 ALIAS Catch2) +include(Catch) + +set(CATCH_EXTRA_ARGS "" CACHE STRING "Extra arguments for catch2 test suites.") + +add_library(test_common INTERFACE) +target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE) +target_link_libraries(test_common INTERFACE Catch2::Catch2) + +if (APPLE) + target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) +endif() + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +add_subdirectory(libnest2d) +add_subdirectory(libslic3r) +add_subdirectory(timeutils) +add_subdirectory(fff_print) +add_subdirectory(sla_print) +# add_subdirectory(example) diff --git a/tests/catch2/LICENSE.txt b/tests/catch2/LICENSE.txt new file mode 100644 index 0000000000..36b7cd93cd --- /dev/null +++ b/tests/catch2/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tests/catch2/VERSION.txt b/tests/catch2/VERSION.txt new file mode 100644 index 0000000000..587a8125d2 --- /dev/null +++ b/tests/catch2/VERSION.txt @@ -0,0 +1,2 @@ +2.9.2 g2c869e1 + diff --git a/tests/catch2/catch.hpp b/tests/catch2/catch.hpp new file mode 100644 index 0000000000..5feb2a4bea --- /dev/null +++ b/tests/catch2/catch.hpp @@ -0,0 +1,17075 @@ +/* + * Catch v2.9.2 + * Generated: 2019-08-08 13:35:12.279703 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 9 +#define CATCH_VERSION_PATCH 2 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#ifdef __clang__ + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if string_view is available and usable +// The check is split apart to work around v140 (VS2015) preprocessor issue... +#if defined(__has_include) +#if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Check if optional is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +//////////////////////////////////////////////////////////////////////////////// +// Check if byte is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_BYTE +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + public: + using size_type = std::size_t; + + private: + friend struct StringRefTestAccess; + + char const* m_start; + size_type m_size; + + char* m_data = nullptr; + + void takeOwnership(); + + static constexpr char const* const s_empty = ""; + + public: // construction/ assignment + StringRef() noexcept + : StringRef( s_empty, 0 ) + {} + + StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef( char const* rawChars ) noexcept; + + StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + ~StringRef() noexcept { + delete[] m_data; + } + + auto operator = ( StringRef const &other ) noexcept -> StringRef& { + delete[] m_data; + m_data = nullptr; + m_start = other.m_start; + m_size = other.m_size; + return *this; + } + + operator std::string() const; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char; + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + // Returns the current start pointer. + // Note that the pointer can change when if the StringRef is a substring + auto currentData() const noexcept -> char const*; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } + +} // namespace Catch + +inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_type_traits.hpp + + +#include + +namespace Catch{ + +#ifdef CATCH_CPP17_OR_GREATER + template + inline constexpr auto is_unique = std::true_type{}; + + template + inline constexpr auto is_unique = std::bool_constant< + (!std::is_same_v && ...) && is_unique + >{}; +#else + +template +struct is_unique : std::true_type{}; + +template +struct is_unique : std::integral_constant +::value + && is_unique::value + && is_unique::value +>{}; + +#endif +} + +// end catch_type_traits.hpp +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + \ + template class L1, typename...E1, template class L2, typename...E2> \ + constexpr auto append(L1, L2) noexcept -> L1 { return {}; }\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + constexpr auto append(L1, L2, Rest...) noexcept -> decltype(append(L1{}, Rest{}...)) { return {}; }\ + template< template class L1, typename...E1, typename...Rest>\ + constexpr auto append(L1, TypeList, Rest...) noexcept -> L1 { return {}; }\ + \ + template< template class Container, template class List, typename...elems>\ + constexpr auto rewrap(List) noexcept -> TypeList> { return {}; }\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + constexpr auto rewrap(List,Elements...) noexcept -> decltype(append(TypeList>{}, rewrap(Elements{}...))) { return {}; }\ + \ + template