mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-12-05 16:51:07 -07:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_git_3010
This commit is contained in:
commit
76c9ddfd3e
147 changed files with 80456 additions and 5413 deletions
|
|
@ -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
|
# Proposal for C++ unit tests and sandboxes
|
||||||
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
|
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
|
# Print out the SLIC3R_* cache options
|
||||||
get_cmake_property(_cache_vars CACHE_VARIABLES)
|
get_cmake_property(_cache_vars CACHE_VARIABLES)
|
||||||
|
|
|
||||||
175
cmake/modules/Catch2/Catch.cmake
Normal file
175
cmake/modules/Catch2/Catch.cmake
Normal file
|
|
@ -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 ``<target>_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 ``<target>_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=$<TARGET_FILE:${TARGET}>"
|
||||||
|
-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
|
||||||
|
)
|
||||||
106
cmake/modules/Catch2/CatchAddTests.cmake
Normal file
106
cmake/modules/Catch2/CatchAddTests.cmake
Normal file
|
|
@ -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}")
|
||||||
225
cmake/modules/Catch2/ParseAndAddCatchTests.cmake
Normal file
225
cmake/modules/Catch2/ParseAndAddCatchTests.cmake
Normal file
|
|
@ -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 "\\\$<TARGET_OBJECTS:.+>")
|
||||||
|
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} $<TARGET_FILE:${TestTarget}> ${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()
|
||||||
|
|
@ -82,6 +82,29 @@ variants = default
|
||||||
technology = SLA
|
technology = SLA
|
||||||
family = SL1
|
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
|
# All presets starting with asterisk, for example *common*, are intermediate and they will
|
||||||
# not make it into the user interface.
|
# not make it into the user interface.
|
||||||
|
|
||||||
|
|
@ -1128,6 +1151,7 @@ filament_density = 3.9
|
||||||
filament_colour = #804040
|
filament_colour = #804040
|
||||||
filament_max_volumetric_speed = 9
|
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_notes = "List of materials tested with standard print settings:\n\nColorFabb bronzeFill\nColorFabb brassFill\nColorFabb steelFill\nColorFabb copperFill"
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb HT]
|
[filament:ColorFabb HT]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1145,11 +1169,13 @@ max_fan_speed = 20
|
||||||
min_fan_speed = 10
|
min_fan_speed = 10
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode"
|
||||||
temperature = 270
|
temperature = 270
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb PLA-PHA]
|
[filament:ColorFabb PLA-PHA]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
filament_cost = 55.5
|
filament_cost = 55.5
|
||||||
filament_density = 1.24
|
filament_density = 1.24
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb woodFill]
|
[filament:ColorFabb woodFill]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
|
|
@ -1163,6 +1189,7 @@ filament_max_volumetric_speed = 10
|
||||||
first_layer_temperature = 200
|
first_layer_temperature = 200
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 200
|
temperature = 200
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb corkFill]
|
[filament:ColorFabb corkFill]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
|
|
@ -1175,6 +1202,7 @@ filament_max_volumetric_speed = 6
|
||||||
first_layer_temperature = 220
|
first_layer_temperature = 220
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 220
|
temperature = 220
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb XT]
|
[filament:ColorFabb XT]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1184,6 +1212,7 @@ filament_density = 1.27
|
||||||
first_layer_bed_temperature = 90
|
first_layer_bed_temperature = 90
|
||||||
first_layer_temperature = 260
|
first_layer_temperature = 260
|
||||||
temperature = 270
|
temperature = 270
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb XT-CF20]
|
[filament:ColorFabb XT-CF20]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1199,6 +1228,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{el
|
||||||
temperature = 260
|
temperature = 260
|
||||||
filament_retract_length = nil
|
filament_retract_length = nil
|
||||||
filament_retract_lift = 0.2
|
filament_retract_lift = 0.2
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb nGen]
|
[filament:ColorFabb nGen]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1211,6 +1241,7 @@ filament_type = NGEN
|
||||||
first_layer_temperature = 240
|
first_layer_temperature = 240
|
||||||
max_fan_speed = 35
|
max_fan_speed = 35
|
||||||
min_fan_speed = 20
|
min_fan_speed = 20
|
||||||
|
filament_vendor = ColorFabb
|
||||||
|
|
||||||
[filament:ColorFabb nGen flex]
|
[filament:ColorFabb nGen flex]
|
||||||
inherits = *FLEX*
|
inherits = *FLEX*
|
||||||
|
|
@ -1231,12 +1262,14 @@ temperature = 260
|
||||||
filament_retract_length = nil
|
filament_retract_length = nil
|
||||||
filament_retract_lift = 0
|
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)
|
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]
|
[filament:E3D Edge]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
filament_cost = 56.9
|
filament_cost = 56.9
|
||||||
filament_density = 1.26
|
filament_density = 1.26
|
||||||
filament_type = EDGE
|
filament_type = EDGE
|
||||||
|
filament_vendor = E3D
|
||||||
|
|
||||||
[filament:E3D PC-ABS]
|
[filament:E3D PC-ABS]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
|
|
@ -1245,6 +1278,7 @@ filament_type = PC
|
||||||
filament_density = 1.05
|
filament_density = 1.05
|
||||||
first_layer_temperature = 270
|
first_layer_temperature = 270
|
||||||
temperature = 270
|
temperature = 270
|
||||||
|
filament_vendor = E3D
|
||||||
|
|
||||||
[filament:Fillamentum ABS]
|
[filament:Fillamentum ABS]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
|
|
@ -1252,6 +1286,7 @@ filament_cost = 32.4
|
||||||
filament_density = 1.04
|
filament_density = 1.04
|
||||||
first_layer_temperature = 240
|
first_layer_temperature = 240
|
||||||
temperature = 240
|
temperature = 240
|
||||||
|
filament_vendor = Fillamentum
|
||||||
|
|
||||||
[filament:Fillamentum ASA]
|
[filament:Fillamentum ASA]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
|
|
@ -1266,6 +1301,7 @@ slowdown_below_layer_time = 15
|
||||||
first_layer_temperature = 265
|
first_layer_temperature = 265
|
||||||
temperature = 265
|
temperature = 265
|
||||||
filament_type = ASA
|
filament_type = ASA
|
||||||
|
filament_vendor = Fillamentum
|
||||||
|
|
||||||
[filament:Prusament ASA]
|
[filament:Prusament ASA]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
|
|
@ -1296,6 +1332,7 @@ first_layer_temperature = 275
|
||||||
max_fan_speed = 50
|
max_fan_speed = 50
|
||||||
min_fan_speed = 50
|
min_fan_speed = 50
|
||||||
temperature = 275
|
temperature = 275
|
||||||
|
filament_vendor = Fillamentum
|
||||||
|
|
||||||
[filament:Fillamentum Timberfill]
|
[filament:Fillamentum Timberfill]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
|
|
@ -1309,24 +1346,28 @@ filament_max_volumetric_speed = 10
|
||||||
first_layer_temperature = 190
|
first_layer_temperature = 190
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 190
|
temperature = 190
|
||||||
|
filament_vendor = Fillamentum
|
||||||
|
|
||||||
[filament:Generic ABS]
|
[filament:Generic ABS]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
filament_cost = 27.82
|
filament_cost = 27.82
|
||||||
filament_density = 1.04
|
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_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]
|
[filament:Generic PET]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
filament_cost = 27.82
|
filament_cost = 27.82
|
||||||
filament_density = 1.27
|
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_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]
|
[filament:Generic PLA]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
filament_cost = 25.4
|
filament_cost = 25.4
|
||||||
filament_density = 1.24
|
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_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]
|
[filament:Generic FLEX]
|
||||||
inherits = *FLEX*
|
inherits = *FLEX*
|
||||||
|
|
@ -1347,6 +1388,7 @@ filament_colour = #3A80CA
|
||||||
first_layer_bed_temperature = 100
|
first_layer_bed_temperature = 100
|
||||||
first_layer_temperature = 270
|
first_layer_temperature = 270
|
||||||
temperature = 270
|
temperature = 270
|
||||||
|
filament_vendor = Polymaker
|
||||||
|
|
||||||
[filament:PrimaSelect PVA+]
|
[filament:PrimaSelect PVA+]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
|
|
@ -1363,12 +1405,14 @@ filament_type = PVA
|
||||||
first_layer_temperature = 195
|
first_layer_temperature = 195
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 195
|
temperature = 195
|
||||||
|
filament_vendor = PrimaSelect
|
||||||
|
|
||||||
[filament:Prusa ABS]
|
[filament:Prusa ABS]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
filament_cost = 27.82
|
filament_cost = 27.82
|
||||||
filament_density = 1.08
|
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_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*]
|
[filament:*ABS MMU2*]
|
||||||
inherits = Prusa ABS
|
inherits = Prusa ABS
|
||||||
|
|
@ -1385,6 +1429,7 @@ filament_unloading_speed = 20
|
||||||
|
|
||||||
[filament:Generic ABS MMU2]
|
[filament:Generic ABS MMU2]
|
||||||
inherits = *ABS MMU2*
|
inherits = *ABS MMU2*
|
||||||
|
filament_vendor = Generic
|
||||||
|
|
||||||
[filament:Prusament ASA MMU2]
|
[filament:Prusament ASA MMU2]
|
||||||
inherits = *ABS 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]
|
[filament:Prusa ABS MMU2]
|
||||||
inherits = *ABS MMU2*
|
inherits = *ABS MMU2*
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:Prusa HIPS]
|
[filament:Prusa HIPS]
|
||||||
inherits = *ABS*
|
inherits = *ABS*
|
||||||
|
|
@ -1428,6 +1474,7 @@ max_fan_speed = 20
|
||||||
min_fan_speed = 20
|
min_fan_speed = 20
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 220
|
temperature = 220
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:Prusa PET]
|
[filament:Prusa PET]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1435,6 +1482,7 @@ filament_cost = 27.82
|
||||||
filament_density = 1.27
|
filament_density = 1.27
|
||||||
filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG"
|
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)
|
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]
|
[filament:Prusament PETG]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1444,12 +1492,14 @@ filament_cost = 24.99
|
||||||
filament_density = 1.27
|
filament_density = 1.27
|
||||||
filament_type = PETG
|
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)
|
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]
|
[filament:Prusa PET 0.6 nozzle]
|
||||||
inherits = *PET06*
|
inherits = *PET06*
|
||||||
filament_cost = 27.82
|
filament_cost = 27.82
|
||||||
filament_density = 1.27
|
filament_density = 1.27
|
||||||
filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG"
|
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]
|
[filament:Prusament PETG 0.6 nozzle]
|
||||||
inherits = *PET06*
|
inherits = *PET06*
|
||||||
|
|
@ -1458,6 +1508,7 @@ temperature = 250
|
||||||
filament_cost = 24.99
|
filament_cost = 24.99
|
||||||
filament_density = 1.27
|
filament_density = 1.27
|
||||||
filament_type = PETG
|
filament_type = PETG
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:*PET MMU2*]
|
[filament:*PET MMU2*]
|
||||||
inherits = Prusa PET
|
inherits = Prusa PET
|
||||||
|
|
@ -1485,9 +1536,11 @@ filament_max_volumetric_speed = 13
|
||||||
|
|
||||||
[filament:Generic PET MMU2]
|
[filament:Generic PET MMU2]
|
||||||
inherits = *PET MMU2*
|
inherits = *PET MMU2*
|
||||||
|
filament_vendor = Generic
|
||||||
|
|
||||||
[filament:Prusa PET MMU2]
|
[filament:Prusa PET MMU2]
|
||||||
inherits = *PET MMU2*
|
inherits = *PET MMU2*
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:Prusament PETG MMU2]
|
[filament:Prusament PETG MMU2]
|
||||||
inherits = *PET MMU2*
|
inherits = *PET MMU2*
|
||||||
|
|
@ -1498,16 +1551,19 @@ inherits = *PET MMU2 06*
|
||||||
|
|
||||||
[filament:Prusa PET MMU2 0.6 nozzle]
|
[filament:Prusa PET MMU2 0.6 nozzle]
|
||||||
inherits = *PET MMU2 06*
|
inherits = *PET MMU2 06*
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:Prusament PETG MMU2 0.6 nozzle]
|
[filament:Prusament PETG MMU2 0.6 nozzle]
|
||||||
inherits = *PET MMU2 06*
|
inherits = *PET MMU2 06*
|
||||||
filament_type = PETG
|
filament_type = PETG
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:Prusa PLA]
|
[filament:Prusa PLA]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
filament_cost = 25.4
|
filament_cost = 25.4
|
||||||
filament_density = 1.24
|
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_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]
|
[filament:Prusament PLA]
|
||||||
inherits = *PLA*
|
inherits = *PLA*
|
||||||
|
|
@ -1515,6 +1571,7 @@ temperature = 215
|
||||||
filament_cost = 24.99
|
filament_cost = 24.99
|
||||||
filament_density = 1.24
|
filament_density = 1.24
|
||||||
filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa"
|
filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa"
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:*PLA MMU2*]
|
[filament:*PLA MMU2*]
|
||||||
inherits = Prusa PLA
|
inherits = Prusa PLA
|
||||||
|
|
@ -1534,18 +1591,22 @@ filament_unloading_speed_start = 100
|
||||||
|
|
||||||
[filament:Generic PLA MMU2]
|
[filament:Generic PLA MMU2]
|
||||||
inherits = *PLA MMU2*
|
inherits = *PLA MMU2*
|
||||||
|
filament_vendor = Generic
|
||||||
|
|
||||||
[filament:Prusa PLA MMU2]
|
[filament:Prusa PLA MMU2]
|
||||||
inherits = *PLA MMU2*
|
inherits = *PLA MMU2*
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:Prusament PLA MMU2]
|
[filament:Prusament PLA MMU2]
|
||||||
inherits = *PLA MMU2*
|
inherits = *PLA MMU2*
|
||||||
|
filament_vendor = Prusa
|
||||||
|
|
||||||
[filament:SemiFlex or Flexfill 98A]
|
[filament:SemiFlex or Flexfill 98A]
|
||||||
inherits = *FLEX*
|
inherits = *FLEX*
|
||||||
filament_cost = 82
|
filament_cost = 82
|
||||||
filament_density = 1.22
|
filament_density = 1.22
|
||||||
filament_max_volumetric_speed = 1.35
|
filament_max_volumetric_speed = 1.35
|
||||||
|
filament_vendor = Flexfill
|
||||||
|
|
||||||
[filament:Taulman Bridge]
|
[filament:Taulman Bridge]
|
||||||
inherits = *common*
|
inherits = *common*
|
||||||
|
|
@ -1567,6 +1628,7 @@ max_fan_speed = 5
|
||||||
min_fan_speed = 0
|
min_fan_speed = 0
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 250
|
temperature = 250
|
||||||
|
filament_vendor = Taulman
|
||||||
|
|
||||||
[filament:Taulman T-Glase]
|
[filament:Taulman T-Glase]
|
||||||
inherits = *PET*
|
inherits = *PET*
|
||||||
|
|
@ -1580,6 +1642,7 @@ first_layer_temperature = 240
|
||||||
max_fan_speed = 5
|
max_fan_speed = 5
|
||||||
min_fan_speed = 0
|
min_fan_speed = 0
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
|
||||||
|
filament_vendor = Taulman
|
||||||
|
|
||||||
[filament:Verbatim BVOH]
|
[filament:Verbatim BVOH]
|
||||||
inherits = *common*
|
inherits = *common*
|
||||||
|
|
@ -1603,6 +1666,7 @@ max_fan_speed = 100
|
||||||
min_fan_speed = 100
|
min_fan_speed = 100
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 210
|
temperature = 210
|
||||||
|
filament_vendor = Verbatim
|
||||||
|
|
||||||
[filament:Verbatim BVOH MMU2]
|
[filament:Verbatim BVOH MMU2]
|
||||||
inherits = Verbatim BVOH
|
inherits = Verbatim BVOH
|
||||||
|
|
@ -1622,6 +1686,7 @@ filament_unload_time = 12
|
||||||
filament_unloading_speed = 20
|
filament_unloading_speed = 20
|
||||||
filament_unloading_speed_start = 100
|
filament_unloading_speed_start = 100
|
||||||
filament_loading_speed_start = 19
|
filament_loading_speed_start = 19
|
||||||
|
filament_vendor = Verbatim
|
||||||
|
|
||||||
[filament:PrimaSelect PVA+ MMU2]
|
[filament:PrimaSelect PVA+ MMU2]
|
||||||
inherits = *common*
|
inherits = *common*
|
||||||
|
|
@ -1660,6 +1725,7 @@ min_print_speed = 15
|
||||||
slowdown_below_layer_time = 20
|
slowdown_below_layer_time = 20
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
|
||||||
temperature = 195
|
temperature = 195
|
||||||
|
filament_vendor = PrimaSelect
|
||||||
|
|
||||||
[filament:Verbatim PP]
|
[filament:Verbatim PP]
|
||||||
inherits = *common*
|
inherits = *common*
|
||||||
|
|
@ -1682,6 +1748,7 @@ max_fan_speed = 100
|
||||||
min_fan_speed = 100
|
min_fan_speed = 100
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
|
||||||
temperature = 220
|
temperature = 220
|
||||||
|
filament_vendor = Verbatim
|
||||||
|
|
||||||
## Filaments MMU1
|
## Filaments MMU1
|
||||||
|
|
||||||
|
|
@ -1899,9 +1966,11 @@ exposure_time = 6
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
|
||||||
[sla_material:BlueCast Keramaster Dental 0.025]
|
[sla_material:BlueCast Keramaster Dental 0.025]
|
||||||
|
material_type = Dental
|
||||||
inherits = *common 0.025*
|
inherits = *common 0.025*
|
||||||
exposure_time = 6
|
exposure_time = 6
|
||||||
initial_exposure_time = 45
|
initial_exposure_time = 45
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast X10 0.025]
|
[sla_material:BlueCast X10 0.025]
|
||||||
inherits = *common 0.025*
|
inherits = *common 0.025*
|
||||||
|
|
@ -1912,6 +1981,7 @@ initial_exposure_time = 100
|
||||||
inherits = *common 0.025*
|
inherits = *common 0.025*
|
||||||
exposure_time = 6
|
exposure_time = 6
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
[sla_material:Prusa Grey Tough 0.025]
|
[sla_material:Prusa Grey Tough 0.025]
|
||||||
inherits = *common 0.025*
|
inherits = *common 0.025*
|
||||||
|
|
@ -1964,31 +2034,38 @@ initial_exposure_time = 35
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7
|
exposure_time = 7
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast Keramaster 0.05]
|
[sla_material:BlueCast Keramaster 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 8
|
exposure_time = 8
|
||||||
initial_exposure_time = 45
|
initial_exposure_time = 45
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast Keramaster Dental 0.05]
|
[sla_material:BlueCast Keramaster Dental 0.05]
|
||||||
|
material_type = Dental
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7
|
exposure_time = 7
|
||||||
initial_exposure_time = 50
|
initial_exposure_time = 50
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast LCD-DLP Original 0.05]
|
[sla_material:BlueCast LCD-DLP Original 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 10
|
exposure_time = 10
|
||||||
initial_exposure_time = 60
|
initial_exposure_time = 60
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast Phrozen Wax 0.05]
|
[sla_material:BlueCast Phrozen Wax 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 16
|
exposure_time = 16
|
||||||
initial_exposure_time = 50
|
initial_exposure_time = 50
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast S+ 0.05]
|
[sla_material:BlueCast S+ 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 9
|
exposure_time = 9
|
||||||
initial_exposure_time = 45
|
initial_exposure_time = 45
|
||||||
|
material_vendor = Bluecast
|
||||||
|
|
||||||
[sla_material:BlueCast X10 0.05]
|
[sla_material:BlueCast X10 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
|
|
@ -1999,26 +2076,31 @@ initial_exposure_time = 100
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 6
|
exposure_time = 6
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = Monocure
|
||||||
|
|
||||||
[sla_material:Monocure 3D Blue Rapid Resin 0.05]
|
[sla_material:Monocure 3D Blue Rapid Resin 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7
|
exposure_time = 7
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = Monocure
|
||||||
|
|
||||||
[sla_material:Monocure 3D Clear Rapid Resin 0.05]
|
[sla_material:Monocure 3D Clear Rapid Resin 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 8
|
exposure_time = 8
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = Monocure
|
||||||
|
|
||||||
[sla_material:Monocure 3D Grey Rapid Resin 0.05]
|
[sla_material:Monocure 3D Grey Rapid Resin 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 10
|
exposure_time = 10
|
||||||
initial_exposure_time = 30
|
initial_exposure_time = 30
|
||||||
|
material_vendor = Monocure
|
||||||
|
|
||||||
[sla_material:Monocure 3D White Rapid Resin 0.05]
|
[sla_material:Monocure 3D White Rapid Resin 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7
|
exposure_time = 7
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = Monocure
|
||||||
|
|
||||||
[sla_material:3DM-HTR140 (high temperature) 0.05]
|
[sla_material:3DM-HTR140 (high temperature) 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
|
|
@ -2034,36 +2116,43 @@ initial_exposure_time = 25
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 20
|
exposure_time = 20
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = 3DM
|
||||||
|
|
||||||
[sla_material:3DM-DENT 0.05]
|
[sla_material:3DM-DENT 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7
|
exposure_time = 7
|
||||||
initial_exposure_time = 45
|
initial_exposure_time = 45
|
||||||
|
material_vendor = 3DM
|
||||||
|
|
||||||
[sla_material:3DM-HR Green 0.05]
|
[sla_material:3DM-HR Green 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 15
|
exposure_time = 15
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = 3DM
|
||||||
|
|
||||||
[sla_material:3DM-HR Red Wine 0.05]
|
[sla_material:3DM-HR Red Wine 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 9
|
exposure_time = 9
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = 3DM
|
||||||
|
|
||||||
[sla_material:3DM-XPRO White 0.05]
|
[sla_material:3DM-XPRO White 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 9
|
exposure_time = 9
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = 3DM
|
||||||
|
|
||||||
[sla_material:FTD Ash Grey 0.05]
|
[sla_material:FTD Ash Grey 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 9
|
exposure_time = 9
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = FTD
|
||||||
|
|
||||||
[sla_material:Harz Labs Model Resin Cherry 0.05]
|
[sla_material:Harz Labs Model Resin Cherry 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 8
|
exposure_time = 8
|
||||||
initial_exposure_time = 45
|
initial_exposure_time = 45
|
||||||
|
material_vendor = Harz Labs
|
||||||
|
|
||||||
[sla_material:Photocentric Hard Grey 0.05]
|
[sla_material:Photocentric Hard Grey 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
|
|
@ -2116,6 +2205,7 @@ initial_exposure_time = 35
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 13
|
exposure_time = 13
|
||||||
initial_exposure_time = 40
|
initial_exposure_time = 40
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
## [sla_material:Prusa Yellow Solid 0.05]
|
## [sla_material:Prusa Yellow Solid 0.05]
|
||||||
## inherits = *common 0.05*
|
## inherits = *common 0.05*
|
||||||
|
|
@ -2126,6 +2216,7 @@ initial_exposure_time = 40
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7.5
|
exposure_time = 7.5
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
## [sla_material:Prusa Transparent Green Tough 0.05]
|
## [sla_material:Prusa Transparent Green Tough 0.05]
|
||||||
## inherits = *common 0.05*
|
## inherits = *common 0.05*
|
||||||
|
|
@ -2136,21 +2227,25 @@ initial_exposure_time = 35
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 6
|
exposure_time = 6
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
[sla_material:Prusa Maroon Tough 0.05]
|
[sla_material:Prusa Maroon Tough 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 7.5
|
exposure_time = 7.5
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
[sla_material:Prusa Pink Tough 0.05]
|
[sla_material:Prusa Pink Tough 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 8
|
exposure_time = 8
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
[sla_material:Prusa Azure Blue Tough 0.05]
|
[sla_material:Prusa Azure Blue Tough 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
exposure_time = 8
|
exposure_time = 8
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
[sla_material:Prusa Transparent Tough 0.05]
|
[sla_material:Prusa Transparent Tough 0.05]
|
||||||
inherits = *common 0.05*
|
inherits = *common 0.05*
|
||||||
|
|
@ -2193,6 +2288,7 @@ initial_exposure_time = 15
|
||||||
inherits = *common 0.035*
|
inherits = *common 0.035*
|
||||||
exposure_time = 6
|
exposure_time = 6
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
########### Materials 0.1
|
########### Materials 0.1
|
||||||
|
|
||||||
|
|
@ -2235,6 +2331,7 @@ initial_exposure_time = 55
|
||||||
inherits = *common 0.1*
|
inherits = *common 0.1*
|
||||||
exposure_time = 8
|
exposure_time = 8
|
||||||
initial_exposure_time = 35
|
initial_exposure_time = 35
|
||||||
|
material_vendor = Prusa
|
||||||
|
|
||||||
[sla_material:Prusa Green Casting 0.1]
|
[sla_material:Prusa Green Casting 0.1]
|
||||||
inherits = *common 0.1*
|
inherits = *common 0.1*
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ const std::string USAGE_STR = {
|
||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
Contour3D create_base_pool(const Polygons &ground_layer,
|
Contour3D create_pad(const Polygons &ground_layer,
|
||||||
const ExPolygons &holes = {},
|
const ExPolygons &holes = {},
|
||||||
const PoolConfig& cfg = PoolConfig());
|
const PadConfig& cfg = PadConfig());
|
||||||
|
|
||||||
Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling,
|
Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling,
|
||||||
double floor_z_mm, double ceiling_z_mm,
|
double floor_z_mm, double ceiling_z_mm,
|
||||||
|
|
@ -45,7 +45,7 @@ int main(const int argc, const char *argv[]) {
|
||||||
model.align_to_origin();
|
model.align_to_origin();
|
||||||
|
|
||||||
ExPolygons ground_slice;
|
ExPolygons ground_slice;
|
||||||
sla::base_plate(model, ground_slice, 0.1f);
|
sla::pad_plate(model, ground_slice, 0.1f);
|
||||||
if(ground_slice.empty()) return EXIT_FAILURE;
|
if(ground_slice.empty()) return EXIT_FAILURE;
|
||||||
|
|
||||||
ground_slice = offset_ex(ground_slice, 0.5);
|
ground_slice = offset_ex(ground_slice, 0.5);
|
||||||
|
|
@ -56,10 +56,10 @@ int main(const int argc, const char *argv[]) {
|
||||||
|
|
||||||
bench.start();
|
bench.start();
|
||||||
|
|
||||||
sla::PoolConfig cfg;
|
sla::PadConfig cfg;
|
||||||
cfg.min_wall_height_mm = 0;
|
cfg.min_wall_height_mm = 0;
|
||||||
cfg.edge_radius_mm = 0;
|
cfg.edge_radius_mm = 0;
|
||||||
mesh = sla::create_base_pool(to_polygons(ground_slice), {}, cfg);
|
mesh = sla::create_pad(to_polygons(ground_slice), {}, cfg);
|
||||||
|
|
||||||
bench.stop();
|
bench.stop();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ namespace agg
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
template<class VertexSource>
|
template<class VertexSource>
|
||||||
void add_path(VertexSource& vs, unsigned path_id=0)
|
void add_path(VertexSource &&vs, unsigned path_id=0)
|
||||||
{
|
{
|
||||||
double x;
|
double x;
|
||||||
double y;
|
double y;
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||||
# Add our own cmake module path.
|
# Add our own cmake module path.
|
||||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
|
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)
|
option(LIBNEST2D_HEADER_ONLY "If enabled static library will not be built." ON)
|
||||||
|
|
||||||
set(GEOMETRY_BACKENDS clipper boost eigen)
|
set(GEOMETRY_BACKENDS clipper boost eigen)
|
||||||
|
|
@ -109,26 +104,3 @@ if(NOT LIBNEST2D_HEADER_ONLY)
|
||||||
target_link_libraries(${LIBNAME} PUBLIC libnest2d)
|
target_link_libraries(${LIBNAME} PUBLIC libnest2d)
|
||||||
target_compile_definitions(${LIBNAME} PUBLIC LIBNEST2D_STATIC)
|
target_compile_definitions(${LIBNAME} PUBLIC LIBNEST2D_STATIC)
|
||||||
endif()
|
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()
|
|
||||||
|
|
|
||||||
|
|
@ -49,91 +49,84 @@ using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
|
||||||
|
|
||||||
extern template class Nester<NfpPlacer, FirstFitSelection>;
|
extern template class Nester<NfpPlacer, FirstFitSelection>;
|
||||||
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
||||||
extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute(
|
extern template std::size_t Nester<NfpPlacer, FirstFitSelection>::execute(
|
||||||
std::vector<Item>::iterator, std::vector<Item>::iterator);
|
std::vector<Item>::iterator, std::vector<Item>::iterator);
|
||||||
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
|
extern template std::size_t Nester<BottomLeftPlacer, FirstFitSelection>::execute(
|
||||||
std::vector<Item>::iterator, std::vector<Item>::iterator);
|
std::vector<Item>::iterator, std::vector<Item>::iterator);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
template<class Placer = NfpPlacer, class Selector = FirstFitSelection>
|
||||||
class Selector = FirstFitSelection,
|
struct NestConfig {
|
||||||
class Iterator = std::vector<Item>::iterator>
|
typename Placer::Config placer_config;
|
||||||
void nest(Iterator from, Iterator to,
|
typename Selector::Config selector_config;
|
||||||
const typename Placer::BinType& bin,
|
using Placement = typename Placer::Config;
|
||||||
Coord dist = 0,
|
using Selection = typename Selector::Config;
|
||||||
const typename Placer::Config& pconf = {},
|
|
||||||
const typename Selector::Config& sconf = {})
|
NestConfig() = default;
|
||||||
{
|
NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {}
|
||||||
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
|
NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {}
|
||||||
nester.execute(from, to);
|
NestConfig(const typename Placer::Config & pcfg,
|
||||||
}
|
const typename Selector::Config &scfg)
|
||||||
|
: placer_config{pcfg}, selector_config{scfg} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
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<class Placer = NfpPlacer,
|
template<class Placer = NfpPlacer,
|
||||||
class Selector = FirstFitSelection,
|
class Selector = FirstFitSelection,
|
||||||
class Iterator = std::vector<Item>::iterator>
|
class Iterator = std::vector<Item>::iterator>
|
||||||
void nest(Iterator from, Iterator to,
|
std::size_t nest(Iterator from, Iterator to,
|
||||||
const typename Placer::BinType& bin,
|
const typename Placer::BinType & bin,
|
||||||
ProgressFunction prg,
|
Coord dist = 0,
|
||||||
StopCondition scond = []() { return false; },
|
const NestConfig<Placer, Selector> &cfg = {},
|
||||||
Coord dist = 0,
|
NestControl ctl = {})
|
||||||
const typename Placer::Config& pconf = {},
|
|
||||||
const typename Selector::Config& sconf = {})
|
|
||||||
{
|
{
|
||||||
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
|
_Nester<Placer, Selector> nester{bin, dist, cfg.placer_config, cfg.selector_config};
|
||||||
if(prg) nester.progressIndicator(prg);
|
if(ctl.progressfn) nester.progressIndicator(ctl.progressfn);
|
||||||
if(scond) nester.stopCondition(scond);
|
if(ctl.stopcond) nester.stopCondition(ctl.stopcond);
|
||||||
nester.execute(from, to);
|
return nester.execute(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LIBNEST2D_STATIC
|
#ifdef LIBNEST2D_STATIC
|
||||||
|
|
||||||
extern template class Nester<NfpPlacer, FirstFitSelection>;
|
extern template class Nester<NfpPlacer, FirstFitSelection>;
|
||||||
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
||||||
|
extern template std::size_t nest(std::vector<Item>::iterator from,
|
||||||
extern template void nest(std::vector<Item>::iterator from,
|
std::vector<Item>::iterator from to,
|
||||||
std::vector<Item>::iterator to,
|
const Box & bin,
|
||||||
const Box& bin,
|
Coord dist,
|
||||||
Coord dist = 0,
|
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
|
||||||
const NfpPlacer::Config& pconf,
|
NestControl ctl);
|
||||||
const FirstFitSelection::Config& sconf);
|
extern template std::size_t nest(std::vector<Item>::iterator from,
|
||||||
|
std::vector<Item>::iterator from to,
|
||||||
extern template void nest(std::vector<Item>::iterator from,
|
const Box & bin,
|
||||||
std::vector<Item>::iterator to,
|
Coord dist,
|
||||||
const Box& bin,
|
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
|
||||||
ProgressFunction prg,
|
NestControl ctl);
|
||||||
StopCondition scond,
|
|
||||||
Coord dist = 0,
|
|
||||||
const NfpPlacer::Config& pconf,
|
|
||||||
const FirstFitSelection::Config& sconf);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
template<class Placer = NfpPlacer,
|
||||||
class Selector = FirstFitSelection,
|
class Selector = FirstFitSelection,
|
||||||
class Container = std::vector<Item>>
|
class Container = std::vector<Item>>
|
||||||
void nest(Container&& cont,
|
std::size_t nest(Container&& cont,
|
||||||
const typename Placer::BinType& bin,
|
const typename Placer::BinType & bin,
|
||||||
Coord dist = 0,
|
Coord dist = 0,
|
||||||
const typename Placer::Config& pconf = {},
|
const NestConfig<Placer, Selector> &cfg = {},
|
||||||
const typename Selector::Config& sconf = {})
|
NestControl ctl = {})
|
||||||
{
|
{
|
||||||
nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, pconf, sconf);
|
return nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, cfg, ctl);
|
||||||
}
|
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
|
||||||
class Selector = FirstFitSelection,
|
|
||||||
class Container = std::vector<Item>>
|
|
||||||
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<Placer, Selector>(cont.begin(), cont.end(), bin, prg, scond, dist,
|
|
||||||
pconf, sconf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,12 @@ public:
|
||||||
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
|
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
|
||||||
|
|
||||||
inline bool isFixed() const noexcept { return fixed_; }
|
inline bool isFixed() const noexcept { return fixed_; }
|
||||||
inline void markAsFixed(bool fixed = true) { fixed_ = fixed; }
|
inline void markAsFixedInBin(int binid)
|
||||||
|
{
|
||||||
|
fixed_ = binid >= 0;
|
||||||
|
binid_ = binid;
|
||||||
|
}
|
||||||
|
|
||||||
inline void binId(int idx) { binid_ = idx; }
|
inline void binId(int idx) { binid_ = idx; }
|
||||||
inline int binId() const noexcept { return binid_; }
|
inline int binId() const noexcept { return binid_; }
|
||||||
|
|
||||||
|
|
@ -748,6 +752,7 @@ template<class PlacementStrategy, class SelectionStrategy >
|
||||||
class _Nester {
|
class _Nester {
|
||||||
using TSel = SelectionStrategyLike<SelectionStrategy>;
|
using TSel = SelectionStrategyLike<SelectionStrategy>;
|
||||||
TSel selector_;
|
TSel selector_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Item = typename PlacementStrategy::Item;
|
using Item = typename PlacementStrategy::Item;
|
||||||
using ShapeType = typename Item::ShapeType;
|
using ShapeType = typename Item::ShapeType;
|
||||||
|
|
@ -824,7 +829,7 @@ public:
|
||||||
* the selection algorithm.
|
* the selection algorithm.
|
||||||
*/
|
*/
|
||||||
template<class It>
|
template<class It>
|
||||||
inline ItemIteratorOnly<It, void> execute(It from, It to)
|
inline ItemIteratorOnly<It, size_t> execute(It from, It to)
|
||||||
{
|
{
|
||||||
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
|
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
|
||||||
if(infl > 0) std::for_each(from, to, [this, infl](Item& item) {
|
if(infl > 0) std::for_each(from, to, [this, infl](Item& item) {
|
||||||
|
|
@ -837,6 +842,8 @@ public:
|
||||||
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
|
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
|
||||||
item.inflate(-infl);
|
item.inflate(-infl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return selector_.getResult().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a progress indicator function object for the selector.
|
/// Set a progress indicator function object for the selector.
|
||||||
|
|
|
||||||
|
|
@ -1122,8 +1122,6 @@ private:
|
||||||
sl::rotate(sh, item.rotation());
|
sl::rotate(sh, item.rotation());
|
||||||
|
|
||||||
Box bb = sl::boundingBox(sh);
|
Box bb = sl::boundingBox(sh);
|
||||||
bb.minCorner() += item.translation();
|
|
||||||
bb.maxCorner() += item.translation();
|
|
||||||
|
|
||||||
Vertex ci, cb;
|
Vertex ci, cb;
|
||||||
auto bbin = sl::boundingBox(bin_);
|
auto bbin = sl::boundingBox(bin_);
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,17 @@ namespace libnest2d {
|
||||||
template class Nester<NfpPlacer, FirstFitSelection>;
|
template class Nester<NfpPlacer, FirstFitSelection>;
|
||||||
template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
||||||
|
|
||||||
template PackGroup nest(std::vector<Item>::iterator from,
|
template std::size_t nest(std::vector<Item>::iterator from,
|
||||||
std::vector<Item>::iterator to,
|
std::vector<Item>::iterator from to,
|
||||||
const Box& bin,
|
const Box & bin,
|
||||||
Coord dist = 0,
|
Coord dist,
|
||||||
const NfpPlacer::Config& pconf,
|
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
|
||||||
const FirstFitSelection::Config& sconf);
|
NestControl ctl);
|
||||||
|
|
||||||
template PackGroup nest(std::vector<Item>::iterator from,
|
template std::size_t nest(std::vector<Item>::iterator from,
|
||||||
std::vector<Item>::iterator to,
|
std::vector<Item>::iterator from to,
|
||||||
const Box& bin,
|
const Box & bin,
|
||||||
ProgressFunction prg,
|
Coord dist,
|
||||||
StopCondition scond,
|
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
|
||||||
Coord dist = 0,
|
NestControl ctl);
|
||||||
const NfpPlacer::Config& pconf,
|
|
||||||
const FirstFitSelection::Config& sconf);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ public:
|
||||||
|
|
||||||
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
|
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
|
||||||
Item& itm = fixeditems[idx];
|
Item& itm = fixeditems[idx];
|
||||||
itm.markAsFixed();
|
itm.markAsFixedInBin(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pck.configure(m_pconf);
|
m_pck.configure(m_pconf);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public:
|
||||||
PointClass max;
|
PointClass max;
|
||||||
bool defined;
|
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) :
|
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
|
||||||
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
|
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
|
||||||
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
|
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||||
|
|
@ -59,7 +59,7 @@ template <class PointClass>
|
||||||
class BoundingBox3Base : public BoundingBoxBase<PointClass>
|
class BoundingBox3Base : public BoundingBoxBase<PointClass>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BoundingBox3Base() : BoundingBoxBase<PointClass>() {};
|
BoundingBox3Base() : BoundingBoxBase<PointClass>() {}
|
||||||
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
||||||
BoundingBoxBase<PointClass>(pmin, pmax)
|
BoundingBoxBase<PointClass>(pmin, pmax)
|
||||||
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
||||||
|
|
@ -100,6 +100,33 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Will prevent warnings caused by non existing definition of template in hpp
|
||||||
|
extern template void BoundingBoxBase<Point>::scale(double factor);
|
||||||
|
extern template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||||
|
extern template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||||
|
extern template void BoundingBoxBase<Point>::offset(coordf_t delta);
|
||||||
|
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||||
|
extern template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||||
|
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||||
|
extern template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||||
|
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||||
|
extern template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||||
|
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||||
|
extern template Point BoundingBoxBase<Point>::size() const;
|
||||||
|
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||||
|
extern template double BoundingBoxBase<Point>::radius() const;
|
||||||
|
extern template double BoundingBoxBase<Vec2d>::radius() const;
|
||||||
|
extern template Point BoundingBoxBase<Point>::center() const;
|
||||||
|
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||||
|
extern template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||||
|
extern template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||||
|
extern template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||||
|
extern template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||||
|
extern template double BoundingBox3Base<Vec3d>::radius() const;
|
||||||
|
extern template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||||
|
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||||
|
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||||
|
|
||||||
class BoundingBox : public BoundingBoxBase<Point>
|
class BoundingBox : public BoundingBoxBase<Point>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -113,9 +140,9 @@ public:
|
||||||
// to encompass the original bounding box.
|
// to encompass the original bounding box.
|
||||||
void align_to_grid(const coord_t cell_size);
|
void align_to_grid(const coord_t cell_size);
|
||||||
|
|
||||||
BoundingBox() : BoundingBoxBase<Point>() {};
|
BoundingBox() : BoundingBoxBase<Point>() {}
|
||||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {};
|
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
|
||||||
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {};
|
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
|
||||||
BoundingBox(const Lines &lines);
|
BoundingBox(const Lines &lines);
|
||||||
|
|
||||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||||
|
|
@ -124,25 +151,25 @@ public:
|
||||||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BoundingBox3() : BoundingBox3Base<Vec3crd>() {};
|
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
|
||||||
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {};
|
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
|
||||||
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {};
|
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {};
|
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
|
||||||
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {};
|
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
|
||||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {};
|
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {};
|
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {}
|
||||||
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {};
|
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {}
|
||||||
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {};
|
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {}
|
||||||
|
|
||||||
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
BridgeDetector::BridgeDetector(
|
BridgeDetector::BridgeDetector(
|
||||||
ExPolygon _expolygon,
|
ExPolygon _expolygon,
|
||||||
const ExPolygonCollection &_lower_slices,
|
const ExPolygons &_lower_slices,
|
||||||
coord_t _spacing) :
|
coord_t _spacing) :
|
||||||
// The original infill polygon, not inflated.
|
// The original infill polygon, not inflated.
|
||||||
expolygons(expolygons_owned),
|
expolygons(expolygons_owned),
|
||||||
// All surfaces of the object supporting this region.
|
// All surfaces of the object supporting this region.
|
||||||
|
|
@ -20,9 +20,9 @@ BridgeDetector::BridgeDetector(
|
||||||
}
|
}
|
||||||
|
|
||||||
BridgeDetector::BridgeDetector(
|
BridgeDetector::BridgeDetector(
|
||||||
const ExPolygons &_expolygons,
|
const ExPolygons &_expolygons,
|
||||||
const ExPolygonCollection &_lower_slices,
|
const ExPolygons &_lower_slices,
|
||||||
coord_t _spacing) :
|
coord_t _spacing) :
|
||||||
// The original infill polygon, not inflated.
|
// The original infill polygon, not inflated.
|
||||||
expolygons(_expolygons),
|
expolygons(_expolygons),
|
||||||
// All surfaces of the object supporting this region.
|
// 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
|
// 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.
|
// 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()).
|
// 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
|
#ifdef SLIC3R_DEBUG
|
||||||
printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size());
|
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
|
// 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
|
// 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) {
|
if (0) {
|
||||||
|
|
@ -271,7 +275,7 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
|
||||||
if (angle == -1) angle = this->angle;
|
if (angle == -1) angle = this->angle;
|
||||||
if (angle == -1) return;
|
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) {
|
for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) {
|
||||||
// get unsupported bridge edges (both contour and holes)
|
// get unsupported bridge edges (both contour and holes)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include "ExPolygonCollection.hpp"
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
@ -21,7 +20,7 @@ public:
|
||||||
// In case the caller gaves us the input polygons by a value, make a copy.
|
// In case the caller gaves us the input polygons by a value, make a copy.
|
||||||
ExPolygons expolygons_owned;
|
ExPolygons expolygons_owned;
|
||||||
// Lower slices, all regions.
|
// Lower slices, all regions.
|
||||||
const ExPolygonCollection &lower_slices;
|
const ExPolygons &lower_slices;
|
||||||
// Scaled extrusion width of the infill.
|
// Scaled extrusion width of the infill.
|
||||||
coord_t spacing;
|
coord_t spacing;
|
||||||
// Angle resolution for the brute force search of the best bridging angle.
|
// Angle resolution for the brute force search of the best bridging angle.
|
||||||
|
|
@ -29,8 +28,8 @@ public:
|
||||||
// The final optimal angle.
|
// The final optimal angle.
|
||||||
double angle;
|
double angle;
|
||||||
|
|
||||||
BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
|
BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_width);
|
||||||
BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_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.
|
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
|
||||||
bool detect_angle(double bridge_direction_override = 0.);
|
bool detect_angle(double bridge_direction_override = 0.);
|
||||||
Polygons coverage(double angle = -1) const;
|
Polygons coverage(double angle = -1) const;
|
||||||
|
|
|
||||||
|
|
@ -176,8 +176,13 @@ add_library(libslic3r STATIC
|
||||||
miniz_extension.cpp
|
miniz_extension.cpp
|
||||||
SLA/SLACommon.hpp
|
SLA/SLACommon.hpp
|
||||||
SLA/SLABoilerPlate.hpp
|
SLA/SLABoilerPlate.hpp
|
||||||
SLA/SLABasePool.hpp
|
SLA/SLAPad.hpp
|
||||||
SLA/SLABasePool.cpp
|
SLA/SLAPad.cpp
|
||||||
|
SLA/SLASupportTreeBuilder.hpp
|
||||||
|
SLA/SLASupportTreeBuildsteps.hpp
|
||||||
|
SLA/SLASupportTreeBuildsteps.cpp
|
||||||
|
SLA/SLASupportTreeBuilder.cpp
|
||||||
|
SLA/SLAConcurrency.hpp
|
||||||
SLA/SLASupportTree.hpp
|
SLA/SLASupportTree.hpp
|
||||||
SLA/SLASupportTree.cpp
|
SLA/SLASupportTree.cpp
|
||||||
SLA/SLASupportTreeIGL.cpp
|
SLA/SLASupportTreeIGL.cpp
|
||||||
|
|
@ -189,6 +194,8 @@ add_library(libslic3r STATIC
|
||||||
SLA/SLARaster.cpp
|
SLA/SLARaster.cpp
|
||||||
SLA/SLARasterWriter.hpp
|
SLA/SLARasterWriter.hpp
|
||||||
SLA/SLARasterWriter.cpp
|
SLA/SLARasterWriter.cpp
|
||||||
|
SLA/ConcaveHull.hpp
|
||||||
|
SLA/ConcaveHull.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
encoding_check(libslic3r)
|
encoding_check(libslic3r)
|
||||||
|
|
@ -215,6 +222,7 @@ target_link_libraries(libslic3r
|
||||||
qhull
|
qhull
|
||||||
semver
|
semver
|
||||||
tbb
|
tbb
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,19 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input)
|
||||||
return retval;
|
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 Slic3rMultiPoints_to_ClipperPaths(const Polylines &input)
|
||||||
{
|
{
|
||||||
ClipperLib::Paths retval;
|
ClipperLib::Paths retval;
|
||||||
|
|
@ -472,14 +485,16 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1,
|
||||||
return union_ex(polys);
|
return union_ex(polys);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template<class T, class TSubj, class TClip>
|
||||||
T
|
T _clipper_do(const ClipperLib::ClipType clipType,
|
||||||
_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject,
|
TSubj && subject,
|
||||||
const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
|
TClip && clip,
|
||||||
|
const ClipperLib::PolyFillType fillType,
|
||||||
|
const bool safety_offset_)
|
||||||
{
|
{
|
||||||
// read input
|
// read input
|
||||||
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
|
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward<TSubj>(subject));
|
||||||
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
|
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(std::forward<TClip>(clip));
|
||||||
|
|
||||||
// perform safety offset
|
// perform safety offset
|
||||||
if (safety_offset_) {
|
if (safety_offset_) {
|
||||||
|
|
@ -648,12 +663,26 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipperLib::PolyTree
|
ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_)
|
||||||
union_pt(const Polygons &subject, bool safety_offset_)
|
|
||||||
{
|
{
|
||||||
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClipperLib::PolyTree union_pt(const ExPolygons &subject, bool safety_offset_)
|
||||||
|
{
|
||||||
|
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperLib::PolyTree union_pt(Polygons &&subject, bool safety_offset_)
|
||||||
|
{
|
||||||
|
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_)
|
||||||
|
{
|
||||||
|
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
|
||||||
|
}
|
||||||
|
|
||||||
Polygons
|
Polygons
|
||||||
union_pt_chained(const Polygons &subject, bool safety_offset_)
|
union_pt_chained(const Polygons &subject, bool safety_offset_)
|
||||||
{
|
{
|
||||||
|
|
@ -664,28 +693,123 @@ union_pt_chained(const Polygons &subject, bool safety_offset_)
|
||||||
return retval;
|
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<e_ordering o>
|
||||||
|
void foreach_node(const ClipperLib::PolyNodes &nodes,
|
||||||
|
std::function<void(const ClipperLib::PolyNode *)> fn);
|
||||||
|
|
||||||
|
template<> void foreach_node<e_ordering::DONT_ORDER_POLYNODES>(
|
||||||
|
const ClipperLib::PolyNodes & nodes,
|
||||||
|
std::function<void(const ClipperLib::PolyNode *)> fn)
|
||||||
|
{
|
||||||
|
for (auto &n : nodes) fn(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void foreach_node<e_ordering::ORDER_POLYNODES>(
|
||||||
|
const ClipperLib::PolyNodes & nodes,
|
||||||
|
std::function<void(const ClipperLib::PolyNode *)> fn)
|
||||||
|
{
|
||||||
|
auto ordered_nodes = order_nodes(nodes);
|
||||||
|
for (auto &n : ordered_nodes) fn(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<e_ordering o>
|
||||||
|
void _traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||||
{
|
{
|
||||||
/* use a nearest neighbor search to order these children
|
/* use a nearest neighbor search to order these children
|
||||||
TODO: supply start_near to chained_path() too? */
|
TODO: supply start_near to chained_path() too? */
|
||||||
|
|
||||||
// collect ordering points
|
|
||||||
Points ordering_points;
|
|
||||||
ordering_points.reserve(nodes.size());
|
|
||||||
for (ClipperLib::PolyNode *pn : nodes)
|
|
||||||
ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y));
|
|
||||||
|
|
||||||
// perform the ordering
|
|
||||||
ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes);
|
|
||||||
|
|
||||||
// push results recursively
|
// push results recursively
|
||||||
for (ClipperLib::PolyNode *pn : ordered_nodes) {
|
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
|
||||||
// traverse the next depth
|
// traverse the next depth
|
||||||
traverse_pt(pn->Childs, retval);
|
_traverse_pt<o>(node->Childs, retval);
|
||||||
retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour));
|
retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour));
|
||||||
if (pn->IsHole())
|
if (node->IsHole()) retval->back().reverse(); // ccw
|
||||||
retval->back().reverse(); // ccw
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<e_ordering o>
|
||||||
|
void _traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval)
|
||||||
|
{
|
||||||
|
if (!retval || !tree) return;
|
||||||
|
|
||||||
|
ExPolygons &retv = *retval;
|
||||||
|
|
||||||
|
std::function<void(const ClipperLib::PolyNode*, ExPolygon&)> 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<o>(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<o>(pptr->Childs, contour_fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
contour_fn(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<e_ordering o>
|
||||||
|
void _traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval)
|
||||||
|
{
|
||||||
|
// Here is the actual traverse
|
||||||
|
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
|
||||||
|
_traverse_pt<o>(node, retval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *retval)
|
||||||
|
{
|
||||||
|
_traverse_pt<e_ordering::ORDER_POLYNODES>(tree, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse_pt_unordered(const ClipperLib::PolyNode *tree, ExPolygons *retval)
|
||||||
|
{
|
||||||
|
_traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(tree, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse_pt(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||||
|
{
|
||||||
|
_traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse_pt(const ClipperLib::PolyNodes &nodes, ExPolygons *retval)
|
||||||
|
{
|
||||||
|
_traverse_pt<e_ordering::ORDER_POLYNODES>(nodes, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||||
|
{
|
||||||
|
_traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, ExPolygons *retval)
|
||||||
|
{
|
||||||
|
_traverse_pt<e_ordering::DONT_ORDER_POLYNODES>(nodes, retval);
|
||||||
}
|
}
|
||||||
|
|
||||||
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ Slic3r::ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree);
|
||||||
|
|
||||||
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input);
|
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input);
|
||||||
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &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);
|
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input);
|
||||||
Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input);
|
Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input);
|
||||||
Slic3r::Polyline ClipperPath_to_Slic3rPolyline(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::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);
|
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 */
|
/* OTHER */
|
||||||
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
||||||
|
|
|
||||||
|
|
@ -271,8 +271,6 @@ ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, Con
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ConfigOptionDef::nocli = "~~~noCLI";
|
|
||||||
|
|
||||||
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
|
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
|
||||||
{
|
{
|
||||||
// prepare a function for wrapping text
|
// prepare a function for wrapping text
|
||||||
|
|
|
||||||
|
|
@ -1444,7 +1444,7 @@ public:
|
||||||
std::vector<std::string> cli_args(const std::string &key) const;
|
std::vector<std::string> cli_args(const std::string &key) const;
|
||||||
|
|
||||||
// Assign this key to cli to disable CLI for this option.
|
// 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.
|
// Map from a config option name to its definition.
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ public:
|
||||||
ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; }
|
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; }
|
ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
|
||||||
|
|
||||||
|
inline explicit ExPolygon(const Polygon &p): contour(p) {}
|
||||||
|
inline explicit ExPolygon(Polygon &&p): contour(std::move(p)) {}
|
||||||
|
|
||||||
Polygon contour;
|
Polygon contour;
|
||||||
Polygons holes;
|
Polygons holes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
|
||||||
ExPolygonCollection::operator Points() const
|
ExPolygonCollection::operator Points() const
|
||||||
{
|
{
|
||||||
Points points;
|
Points points;
|
||||||
Polygons pp = *this;
|
Polygons pp = (Polygons)*this;
|
||||||
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
|
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
|
||||||
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
|
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
|
||||||
points.push_back(*point);
|
points.push_back(*point);
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ public:
|
||||||
ExPolygons expolygons;
|
ExPolygons expolygons;
|
||||||
|
|
||||||
ExPolygonCollection() {}
|
ExPolygonCollection() {}
|
||||||
ExPolygonCollection(const ExPolygon &expolygon);
|
explicit ExPolygonCollection(const ExPolygon &expolygon);
|
||||||
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
|
explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
|
||||||
operator Points() const;
|
explicit operator Points() const;
|
||||||
operator Polygons() const;
|
explicit operator Polygons() const;
|
||||||
operator ExPolygons&();
|
explicit operator ExPolygons&();
|
||||||
void scale(double factor);
|
void scale(double factor);
|
||||||
void translate(double x, double y);
|
void translate(double x, double y);
|
||||||
void rotate(double angle, const Point ¢er);
|
void rotate(double angle, const Point ¢er);
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ namespace Slic3r {
|
||||||
|
|
||||||
void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
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
|
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)
|
void ExtrusionPath::clip_end(double distance)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ namespace pt = boost::property_tree;
|
||||||
// VERSION NUMBERS
|
// VERSION NUMBERS
|
||||||
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
|
// 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.
|
// 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 char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
|
||||||
|
|
||||||
const std::string MODEL_FOLDER = "3D/";
|
const std::string MODEL_FOLDER = "3D/";
|
||||||
|
|
@ -87,6 +88,13 @@ const char* VOLUME_TYPE = "volume";
|
||||||
const char* NAME_KEY = "name";
|
const char* NAME_KEY = "name";
|
||||||
const char* MODIFIER_KEY = "modifier";
|
const char* MODIFIER_KEY = "modifier";
|
||||||
const char* VOLUME_TYPE_KEY = "volume_type";
|
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 unsigned int VALID_OBJECT_TYPES_COUNT = 1;
|
||||||
const char* VALID_OBJECT_TYPES[] =
|
const char* VALID_OBJECT_TYPES[] =
|
||||||
|
|
@ -148,11 +156,15 @@ bool get_attribute_value_bool(const char** attributes, unsigned int attributes_s
|
||||||
return (text != nullptr) ? (bool)::atoi(text) : true;
|
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())
|
if (mat_str.empty())
|
||||||
// empty string means default identity matrix
|
// empty string means default identity matrix
|
||||||
return Slic3r::Transform3d::Identity();
|
return ret;
|
||||||
|
|
||||||
std::vector<std::string> mat_elements_str;
|
std::vector<std::string> mat_elements_str;
|
||||||
boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on);
|
boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on);
|
||||||
|
|
@ -160,9 +172,8 @@ Slic3r::Transform3d get_transform_from_string(const std::string& mat_str)
|
||||||
unsigned int size = (unsigned int)mat_elements_str.size();
|
unsigned int size = (unsigned int)mat_elements_str.size();
|
||||||
if (size != 12)
|
if (size != 12)
|
||||||
// invalid data, return identity matrix
|
// invalid data, return identity matrix
|
||||||
return Slic3r::Transform3d::Identity();
|
return ret;
|
||||||
|
|
||||||
Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
|
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
// matrices are stored into 3mf files as 4x3
|
// matrices are stored into 3mf files as 4x3
|
||||||
// we need to transpose them
|
// we need to transpose them
|
||||||
|
|
@ -1375,7 +1386,7 @@ namespace Slic3r {
|
||||||
bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
|
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);
|
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);
|
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
|
||||||
if (object_item == m_objects.end())
|
if (object_item == m_objects.end())
|
||||||
|
|
@ -1421,7 +1432,7 @@ namespace Slic3r {
|
||||||
// see specifications
|
// see specifications
|
||||||
|
|
||||||
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
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);
|
int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
||||||
|
|
||||||
return _create_object_instance(object_id, transform, printable, 1);
|
return _create_object_instance(object_id, transform, printable, 1);
|
||||||
|
|
@ -1634,6 +1645,21 @@ namespace Slic3r {
|
||||||
return false;
|
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
|
// splits volume out of imported geometry
|
||||||
TriangleMesh triangle_mesh;
|
TriangleMesh triangle_mesh;
|
||||||
stl_file &stl = triangle_mesh.stl;
|
stl_file &stl = triangle_mesh.stl;
|
||||||
|
|
@ -1651,7 +1677,12 @@ namespace Slic3r {
|
||||||
stl_facet& facet = stl.facet_start[i];
|
stl_facet& facet = stl.facet_start[i];
|
||||||
for (unsigned int v = 0; v < 3; ++v)
|
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<double>()).cast<float>();
|
||||||
|
::memcpy(facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1659,10 +1690,12 @@ namespace Slic3r {
|
||||||
triangle_mesh.repair();
|
triangle_mesh.repair();
|
||||||
|
|
||||||
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
|
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();
|
volume->calculate_convex_hull();
|
||||||
|
|
||||||
// apply volume's name and config data
|
// apply the remaining volume's metadata
|
||||||
for (const Metadata& metadata : volume_data.metadata)
|
for (const Metadata& metadata : volume_data.metadata)
|
||||||
{
|
{
|
||||||
if (metadata.key == NAME_KEY)
|
if (metadata.key == NAME_KEY)
|
||||||
|
|
@ -1671,6 +1704,18 @@ namespace Slic3r {
|
||||||
volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
||||||
else if (metadata.key == VOLUME_TYPE_KEY)
|
else if (metadata.key == VOLUME_TYPE_KEY)
|
||||||
volume->set_type(ModelVolume::type_from_string(metadata.value));
|
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
|
else
|
||||||
volume->config.set_deserialize(metadata.key, metadata.value);
|
volume->config.set_deserialize(metadata.key, metadata.value);
|
||||||
}
|
}
|
||||||
|
|
@ -2116,7 +2161,7 @@ namespace Slic3r {
|
||||||
|
|
||||||
for (const BuildItem& item : build_items)
|
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 c = 0; c < 4; ++c)
|
||||||
{
|
{
|
||||||
for (unsigned r = 0; r < 3; ++r)
|
for (unsigned r = 0; r < 3; ++r)
|
||||||
|
|
@ -2126,7 +2171,7 @@ namespace Slic3r {
|
||||||
stream << " ";
|
stream << " ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stream << "\" printable =\"" << item.printable << "\" />\n";
|
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\" />\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
stream << " </" << BUILD_TAG << ">\n";
|
stream << " </" << BUILD_TAG << ">\n";
|
||||||
|
|
@ -2344,6 +2389,31 @@ namespace Slic3r {
|
||||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
|
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
|
||||||
VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
|
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
|
// stores volume's config data
|
||||||
for (const std::string& key : volume->config.keys())
|
for (const std::string& key : volume->config.keys())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "../PrintConfig.hpp"
|
#include "../PrintConfig.hpp"
|
||||||
#include "../Utils.hpp"
|
#include "../Utils.hpp"
|
||||||
#include "../I18N.hpp"
|
#include "../I18N.hpp"
|
||||||
|
#include "../Geometry.hpp"
|
||||||
|
|
||||||
#include "AMF.hpp"
|
#include "AMF.hpp"
|
||||||
|
|
||||||
|
|
@ -36,7 +37,8 @@
|
||||||
// Added x and y components of rotation
|
// Added x and y components of rotation
|
||||||
// Added x, y and z components of scale
|
// Added x, y and z components of scale
|
||||||
// Added x, y and z components of mirror
|
// 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* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
|
||||||
|
|
||||||
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
|
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
|
||||||
|
|
@ -560,15 +562,30 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||||
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
|
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
|
||||||
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
||||||
stl_allocate(&stl);
|
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();) {
|
for (size_t i = 0; i < m_volume_facets.size();) {
|
||||||
stl_facet &facet = stl.facet_start[i/3];
|
stl_facet &facet = stl.facet_start[i/3];
|
||||||
for (unsigned int v = 0; v < 3; ++ v)
|
for (unsigned int v = 0; v < 3; ++v)
|
||||||
memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
|
{
|
||||||
|
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<double>()).cast<float>();
|
||||||
|
::memcpy((void*)facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stl_get_size(&stl);
|
stl_get_size(&stl);
|
||||||
mesh.repair();
|
mesh.repair();
|
||||||
m_volume->set_mesh(std::move(mesh));
|
m_volume->set_mesh(std::move(mesh));
|
||||||
m_volume->center_geometry_after_creation();
|
// 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->calculate_convex_hull();
|
||||||
m_volume_facets.clear();
|
m_volume_facets.clear();
|
||||||
m_volume = nullptr;
|
m_volume = nullptr;
|
||||||
|
|
@ -664,6 +681,29 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||||
} else if (strcmp(opt_key, "volume_type") == 0) {
|
} else if (strcmp(opt_key, "volume_type") == 0) {
|
||||||
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
|
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) {
|
} else if (m_path.size() == 3) {
|
||||||
if (m_path[1] == NODE_TYPE_MATERIAL) {
|
if (m_path[1] == NODE_TYPE_MATERIAL) {
|
||||||
|
|
@ -1057,7 +1097,28 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
||||||
if (volume->is_modifier())
|
if (volume->is_modifier())
|
||||||
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
|
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
|
||||||
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
|
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
|
||||||
const indexed_triangle_set &its = volume->mesh().its;
|
stream << " <metadata type=\"slic3r.matrix\">";
|
||||||
|
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 << "</metadata>\n";
|
||||||
|
if (!volume->source.input_file.empty())
|
||||||
|
{
|
||||||
|
stream << " <metadata type=\"slic3r.source_file\">" << xml_escape(volume->source.input_file) << "</metadata>\n";
|
||||||
|
stream << " <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
|
||||||
|
stream << " <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
|
||||||
|
stream << " <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";
|
||||||
|
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
|
||||||
|
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
|
||||||
|
}
|
||||||
|
const indexed_triangle_set &its = volume->mesh().its;
|
||||||
for (size_t i = 0; i < its.indices.size(); ++i) {
|
for (size_t i = 0; i < its.indices.size(); ++i) {
|
||||||
stream << " <triangle>\n";
|
stream << " <triangle>\n";
|
||||||
for (int j = 0; j < 3; ++j)
|
for (int j = 0; j < 3; ++j)
|
||||||
|
|
|
||||||
|
|
@ -15,39 +15,41 @@
|
||||||
|
|
||||||
namespace Slic3r {
|
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.
|
// Parse the OBJ file.
|
||||||
ObjParser::ObjData data;
|
ObjParser::ObjData data;
|
||||||
if (! ObjParser::objparse(path, 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the faces and verify, that all faces are triangular.
|
// Count the faces and verify, that all faces are triangular.
|
||||||
size_t num_faces = 0;
|
size_t num_faces = 0;
|
||||||
size_t num_quads = 0;
|
size_t num_quads = 0;
|
||||||
for (size_t i = 0; i < data.vertices.size(); ) {
|
for (size_t i = 0; i < data.vertices.size(); ) {
|
||||||
size_t j = i;
|
size_t j = i;
|
||||||
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
|
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
|
||||||
if (i == j)
|
if (i == j)
|
||||||
continue;
|
continue;
|
||||||
size_t face_vertices = j - i;
|
size_t face_vertices = j - i;
|
||||||
if (face_vertices != 3 && face_vertices != 4) {
|
if (face_vertices != 3 && face_vertices != 4) {
|
||||||
// Non-triangular and non-quad faces are not supported as of now.
|
// Non-triangular and non-quad faces are not supported as of now.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (face_vertices == 4)
|
if (face_vertices == 4)
|
||||||
++ num_quads;
|
++ num_quads;
|
||||||
++ num_faces;
|
++ num_faces;
|
||||||
i = j + 1;
|
i = j + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert ObjData into STL.
|
// Convert ObjData into STL.
|
||||||
TriangleMesh mesh;
|
TriangleMesh &mesh = *meshptr;
|
||||||
stl_file &stl = mesh.stl;
|
stl_file &stl = mesh.stl;
|
||||||
stl.stats.type = inmemory;
|
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.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 clears all the allocated data to zero, all normals are set to zeros as well.
|
||||||
stl_allocate(&stl);
|
stl_allocate(&stl);
|
||||||
|
|
@ -68,14 +70,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
|
||||||
++ num_normals;
|
++ num_normals;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.vertices[i].coordIdx != -1) {
|
if (data.vertices[i].coordIdx != -1) {
|
||||||
// This is a quad. Produce the other triangle.
|
// This is a quad. Produce the other triangle.
|
||||||
stl_facet &facet2 = stl.facet_start[i_face++];
|
stl_facet &facet2 = stl.facet_start[i_face++];
|
||||||
facet2.vertex[0] = facet.vertex[0];
|
facet2.vertex[0] = facet.vertex[0];
|
||||||
facet2.vertex[1] = facet.vertex[2];
|
facet2.vertex[1] = facet.vertex[2];
|
||||||
const ObjParser::ObjVertex &vertex = data.vertices[i++];
|
const ObjParser::ObjVertex &vertex = data.vertices[i++];
|
||||||
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
|
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
|
||||||
if (vertex.normalIdx != -1) {
|
if (vertex.normalIdx != -1) {
|
||||||
normal(0) += data.normals[vertex.normalIdx*3];
|
normal(0) += data.normals[vertex.normalIdx*3];
|
||||||
normal(1) += data.normals[vertex.normalIdx*3+1];
|
normal(1) += data.normals[vertex.normalIdx*3+1];
|
||||||
normal(2) += data.normals[vertex.normalIdx*3+2];
|
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)
|
if (len > EPSILON)
|
||||||
facet.normal = normal / len;
|
facet.normal = normal / len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stl_get_size(&stl);
|
stl_get_size(&stl);
|
||||||
mesh.repair();
|
mesh.repair();
|
||||||
if (mesh.facets_count() == 0) {
|
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;
|
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;
|
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)
|
bool store_obj(const char *path, TriangleMesh *mesh)
|
||||||
{
|
{
|
||||||
//FIXME returning false even if write failed.
|
//FIXME returning false even if write failed.
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ namespace Slic3r {
|
||||||
|
|
||||||
class TriangleMesh;
|
class TriangleMesh;
|
||||||
class Model;
|
class Model;
|
||||||
|
class ModelObject;
|
||||||
|
|
||||||
// Load an OBJ file into a provided model.
|
// 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 load_obj(const char *path, Model *model, const char *object_name = nullptr);
|
||||||
|
|
||||||
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
||||||
|
|
|
||||||
|
|
@ -507,7 +507,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
|
||||||
std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
|
std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
|
||||||
{
|
{
|
||||||
std::string gcode;
|
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_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
|
||||||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||||
if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// We need to get position and angle of the wipe tower to transform them to actual position.
|
||||||
Transform2d trafo =
|
Transform2d trafo =
|
||||||
Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) *
|
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;
|
BoundingBoxf bbox;
|
||||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||||
|
|
|
||||||
|
|
@ -787,8 +787,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.
|
// 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.
|
// Extrude 4 rounds of a brim around the future wipe tower.
|
||||||
box_coordinates box(wipeTower_box);
|
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) {
|
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)
|
writer.travel (box.ld, 7000)
|
||||||
.extrude(box.lu, 2100).extrude(box.ru)
|
.extrude(box.lu, 2100).extrude(box.ru)
|
||||||
.extrude(box.rd ).extrude(box.ld);
|
.extrude(box.rd ).extrude(box.ld);
|
||||||
|
|
@ -800,6 +802,10 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of
|
||||||
writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n"
|
writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n"
|
||||||
";-----------------------------------\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
|
m_print_brim = false; // Mark the brim as extruded
|
||||||
|
|
||||||
// Ask our writer about how much material was consumed:
|
// Ask our writer about how much material was consumed:
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ public:
|
||||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||||
|
|
||||||
float get_depth() const { return m_wipe_tower_depth; }
|
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.
|
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_width; // Width of the wipe tower.
|
||||||
float m_wipe_tower_depth = 0.f; // Depth 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_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_internal_rotation = 0.f;
|
||||||
float m_y_shift = 0.f; // y shift passed to writer
|
float m_y_shift = 0.f; // y shift passed to writer
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
#include "SVG.hpp"
|
#include "SVG.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1329,6 +1332,32 @@ void Transformation::set_from_transform(const Transform3d& transform)
|
||||||
// std::cout << "something went wrong in extracting data from matrix" << std::endl;
|
// 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<std::string> 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()
|
void Transformation::reset()
|
||||||
{
|
{
|
||||||
m_offset = Vec3d::Zero();
|
m_offset = Vec3d::Zero();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
using boost::polygon::voronoi_builder;
|
using boost::polygon::voronoi_builder;
|
||||||
using boost::polygon::voronoi_diagram;
|
using boost::polygon::voronoi_diagram;
|
||||||
|
|
||||||
|
namespace ClipperLib {
|
||||||
|
class PolyNode;
|
||||||
|
using PolyNodes = std::vector<PolyNode*>;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Slic3r { namespace Geometry {
|
namespace Slic3r { namespace Geometry {
|
||||||
|
|
||||||
// Generic result of an orientation predicate.
|
// Generic result of an orientation predicate.
|
||||||
|
|
@ -275,6 +280,7 @@ public:
|
||||||
void set_mirror(Axis axis, double mirror);
|
void set_mirror(Axis axis, double mirror);
|
||||||
|
|
||||||
void set_from_transform(const Transform3d& transform);
|
void set_from_transform(const Transform3d& transform);
|
||||||
|
void set_from_string(const std::string& transform_str);
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,22 +252,15 @@ template<class T> struct remove_cvref
|
||||||
|
|
||||||
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
|
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
|
||||||
|
|
||||||
template<template<class> class C, class T>
|
|
||||||
class Container : public C<remove_cvref_t<T>>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit Container(size_t count, T &&initval)
|
|
||||||
: C<remove_cvref_t<T>>(count, initval)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class T> using DefaultContainer = std::vector<T>;
|
template<class T> using DefaultContainer = std::vector<T>;
|
||||||
|
|
||||||
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
||||||
template<class T, class I, template<class> class C = DefaultContainer>
|
template<class T, class I, template<class> class Container = DefaultContainer>
|
||||||
inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n)
|
inline Container<remove_cvref_t<T>> linspace(const T &start,
|
||||||
|
const T &stop,
|
||||||
|
const I &n)
|
||||||
{
|
{
|
||||||
Container<C, T> vals(n, T());
|
Container<remove_cvref_t<T>> vals(n, T());
|
||||||
|
|
||||||
T stride = (stop - start) / n;
|
T stride = (stop - start) / n;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
@ -282,10 +275,13 @@ inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n)
|
||||||
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
||||||
/// leaving 'stride' space between each value.
|
/// leaving 'stride' space between each value.
|
||||||
/// Very similar to Matlab [start:stride:end] notation.
|
/// Very similar to Matlab [start:stride:end] notation.
|
||||||
template<class T, template<class> class C = DefaultContainer>
|
template<class T, template<class> class Container = DefaultContainer>
|
||||||
inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride)
|
inline Container<remove_cvref_t<T>> grid(const T &start,
|
||||||
|
const T &stop,
|
||||||
|
const T &stride)
|
||||||
{
|
{
|
||||||
Container<C, T> vals(size_t(std::ceil((stop - start) / stride)), T());
|
Container<remove_cvref_t<T>>
|
||||||
|
vals(size_t(std::ceil((stop - start) / stride)), T());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||||
|
|
@ -387,10 +383,12 @@ unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
|
||||||
return v.template cast<Tout>() * SCALING_FACTOR;
|
return v.template cast<Tout>() * SCALING_FACTOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T> inline std::vector<T> reserve_vector(size_t capacity)
|
template<class T, class I, class... Args> // Arbitrary allocator can be used
|
||||||
|
inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
|
||||||
{
|
{
|
||||||
std::vector<T> ret;
|
std::vector<T, Args...> ret;
|
||||||
ret.reserve(capacity);
|
if (capacity > I(0)) ret.reserve(size_t(capacity));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,12 +141,12 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||||
|
|
||||||
for (ModelObject *o : model.objects)
|
for (ModelObject *o : model.objects)
|
||||||
{
|
{
|
||||||
if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
// 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
|
// // 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.", ".");
|
// o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
|
||||||
}
|
// }
|
||||||
else
|
// else
|
||||||
o->input_file = input_file;
|
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;
|
new_object->input_file = path;
|
||||||
ModelVolume *new_volume = new_object->add_volume(mesh);
|
ModelVolume *new_volume = new_object->add_volume(mesh);
|
||||||
new_volume->name = name;
|
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();
|
new_object->invalidate_bounding_box();
|
||||||
return new_object;
|
return new_object;
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +185,9 @@ ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh
|
||||||
new_object->input_file = path;
|
new_object->input_file = path;
|
||||||
ModelVolume *new_volume = new_object->add_volume(std::move(mesh));
|
ModelVolume *new_volume = new_object->add_volume(std::move(mesh));
|
||||||
new_volume->name = name;
|
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();
|
new_object->invalidate_bounding_box();
|
||||||
return new_object;
|
return new_object;
|
||||||
}
|
}
|
||||||
|
|
@ -1543,7 +1549,7 @@ bool ModelVolume::is_splittable() const
|
||||||
return m_is_splittable == 1;
|
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();
|
Vec3d shift = this->mesh().bounding_box().center();
|
||||||
if (!shift.isApprox(Vec3d::Zero()))
|
if (!shift.isApprox(Vec3d::Zero()))
|
||||||
|
|
@ -1554,6 +1560,9 @@ void ModelVolume::center_geometry_after_creation()
|
||||||
const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
|
||||||
translate(shift);
|
translate(shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (update_source_offset)
|
||||||
|
source.mesh_offset = shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelVolume::calculate_convex_hull()
|
void ModelVolume::calculate_convex_hull()
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,18 @@ class ModelVolume final : public ObjectBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string name;
|
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<class Archive> void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset); }
|
||||||
|
};
|
||||||
|
Source source;
|
||||||
|
|
||||||
// The triangular model.
|
// The triangular model.
|
||||||
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
||||||
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
|
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(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.
|
// 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!
|
// 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();
|
void calculate_convex_hull();
|
||||||
const TriangleMesh& get_convex_hull() const;
|
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.
|
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
||||||
ModelVolume(ModelObject *object, const ModelVolume &other) :
|
ModelVolume(ModelObject *object, const ModelVolume &other) :
|
||||||
ObjectBase(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().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||||
assert(this->id() == other.id() && this->config.id() == other.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.
|
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||||
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
|
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().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
|
||||||
assert(this->id() != other.id() && this->config.id() == other.config.id());
|
assert(this->id() != other.id() && this->config.id() == other.config.id());
|
||||||
|
|
@ -558,8 +570,8 @@ private:
|
||||||
}
|
}
|
||||||
template<class Archive> void load(Archive &ar) {
|
template<class Archive> void load(Archive &ar) {
|
||||||
bool has_convex_hull;
|
bool has_convex_hull;
|
||||||
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
||||||
cereal::load_by_value(ar, config);
|
cereal::load_by_value(ar, config);
|
||||||
assert(m_mesh);
|
assert(m_mesh);
|
||||||
if (has_convex_hull) {
|
if (has_convex_hull) {
|
||||||
cereal::load_optional(ar, m_convex_hull);
|
cereal::load_optional(ar, m_convex_hull);
|
||||||
|
|
@ -571,8 +583,8 @@ private:
|
||||||
}
|
}
|
||||||
template<class Archive> void save(Archive &ar) const {
|
template<class Archive> void save(Archive &ar) const {
|
||||||
bool has_convex_hull = m_convex_hull.get() != nullptr;
|
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);
|
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
||||||
cereal::save_by_value(ar, config);
|
cereal::save_by_value(ar, config);
|
||||||
if (has_convex_hull)
|
if (has_convex_hull)
|
||||||
cereal::save_optional(ar, m_convex_hull);
|
cereal::save_optional(ar, m_convex_hull);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,11 +136,11 @@ Polyline MotionPlanner::shortest_path(const Point &from, const Point &to)
|
||||||
if (! grown_env.contains(from)) {
|
if (! grown_env.contains(from)) {
|
||||||
// delete second point while the line connecting first to third crosses the
|
// delete second point while the line connecting first to third crosses the
|
||||||
// boundaries as many times as the current first to second
|
// 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);
|
polyline.points.erase(polyline.points.begin() + 1);
|
||||||
}
|
}
|
||||||
if (! grown_env.contains(to))
|
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);
|
polyline.points.erase(polyline.points.end() - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,10 +143,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
||||||
"use_relative_e_distances",
|
"use_relative_e_distances",
|
||||||
"use_volumetric_e",
|
"use_volumetric_e",
|
||||||
"variable_layer_height",
|
"variable_layer_height",
|
||||||
"wipe",
|
"wipe"
|
||||||
"wipe_tower_x",
|
|
||||||
"wipe_tower_y",
|
|
||||||
"wipe_tower_rotation_angle"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_set<std::string> steps_ignore;
|
static std::unordered_set<std::string> steps_ignore;
|
||||||
|
|
@ -167,7 +164,10 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
||||||
|| opt_key == "skirt_height"
|
|| opt_key == "skirt_height"
|
||||||
|| opt_key == "skirt_distance"
|
|| opt_key == "skirt_distance"
|
||||||
|| opt_key == "min_skirt_length"
|
|| opt_key == "min_skirt_length"
|
||||||
|| opt_key == "ooze_prevention") {
|
|| opt_key == "ooze_prevention"
|
||||||
|
|| opt_key == "wipe_tower_x"
|
||||||
|
|| opt_key == "wipe_tower_y"
|
||||||
|
|| opt_key == "wipe_tower_rotation_angle") {
|
||||||
steps.emplace_back(psSkirt);
|
steps.emplace_back(psSkirt);
|
||||||
} else if (opt_key == "brim_width") {
|
} else if (opt_key == "brim_width") {
|
||||||
steps.emplace_back(psBrim);
|
steps.emplace_back(psBrim);
|
||||||
|
|
@ -208,6 +208,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
||||||
|| opt_key == "extra_loading_move"
|
|| opt_key == "extra_loading_move"
|
||||||
|| opt_key == "z_offset") {
|
|| opt_key == "z_offset") {
|
||||||
steps.emplace_back(psWipeTower);
|
steps.emplace_back(psWipeTower);
|
||||||
|
steps.emplace_back(psSkirt);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "first_layer_extrusion_width"
|
opt_key == "first_layer_extrusion_width"
|
||||||
|| opt_key == "min_layer_height"
|
|| opt_key == "min_layer_height"
|
||||||
|
|
@ -1186,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).");
|
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
|
||||||
if (m_config.ooze_prevention)
|
if (m_config.ooze_prevention)
|
||||||
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
|
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) {
|
if (m_objects.size() > 1) {
|
||||||
bool has_custom_layering = false;
|
bool has_custom_layering = false;
|
||||||
|
|
@ -1502,6 +1505,14 @@ void Print::process()
|
||||||
obj->infill();
|
obj->infill();
|
||||||
for (PrintObject *obj : m_objects)
|
for (PrintObject *obj : m_objects)
|
||||||
obj->generate_support_material();
|
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)) {
|
if (this->set_started(psSkirt)) {
|
||||||
m_skirt.clear();
|
m_skirt.clear();
|
||||||
if (this->has_skirt()) {
|
if (this->has_skirt()) {
|
||||||
|
|
@ -1518,14 +1529,6 @@ void Print::process()
|
||||||
}
|
}
|
||||||
this->set_done(psBrim);
|
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();
|
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1602,6 +1605,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)
|
if (points.size() < 3)
|
||||||
// At least three points required for a convex hull.
|
// At least three points required for a convex hull.
|
||||||
return;
|
return;
|
||||||
|
|
@ -1864,6 +1878,22 @@ bool Print::has_wipe_tower() const
|
||||||
m_config.nozzle_diameter.values.size() > 1;
|
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<Print*>(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1);
|
||||||
|
const_cast<Print*>(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_wipe_tower_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Print::_make_wipe_tower()
|
void Print::_make_wipe_tower()
|
||||||
{
|
{
|
||||||
m_wipe_tower_data.clear();
|
m_wipe_tower_data.clear();
|
||||||
|
|
@ -1972,6 +2002,7 @@ void Print::_make_wipe_tower()
|
||||||
m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
|
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);
|
wipe_tower.generate(m_wipe_tower_data.tool_changes);
|
||||||
m_wipe_tower_data.depth = wipe_tower.get_depth();
|
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.
|
// Unload the current filament over the purge tower.
|
||||||
coordf_t layer_height = m_objects.front()->config().layer_height.value;
|
coordf_t layer_height = m_objects.front()->config().layer_height.value;
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ struct WipeTowerData
|
||||||
|
|
||||||
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
||||||
float depth;
|
float depth;
|
||||||
|
float brim_width;
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
tool_ordering.clear();
|
tool_ordering.clear();
|
||||||
|
|
@ -235,6 +236,7 @@ struct WipeTowerData
|
||||||
used_filament.clear();
|
used_filament.clear();
|
||||||
number_of_toolchanges = -1;
|
number_of_toolchanges = -1;
|
||||||
depth = 0.f;
|
depth = 0.f;
|
||||||
|
brim_width = 0.f;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -314,7 +316,6 @@ public:
|
||||||
|
|
||||||
bool has_infinite_skirt() const;
|
bool has_infinite_skirt() const;
|
||||||
bool has_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.
|
// Returns an empty string if valid, otherwise returns an error message.
|
||||||
std::string validate() const override;
|
std::string validate() const override;
|
||||||
|
|
@ -353,7 +354,7 @@ public:
|
||||||
|
|
||||||
// Wipe tower support.
|
// Wipe tower support.
|
||||||
bool has_wipe_tower() const;
|
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;
|
std::string output_filename(const std::string &filename_base = std::string()) const override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -749,6 +749,10 @@ void PrintConfigDef::init_fff_params()
|
||||||
def->set_default_value(new ConfigOptionStrings { "" });
|
def->set_default_value(new ConfigOptionStrings { "" });
|
||||||
def->cli = ConfigOptionDef::nocli;
|
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 = this->add("fill_angle", coFloat);
|
||||||
def->label = L("Fill angle");
|
def->label = L("Fill angle");
|
||||||
def->category = L("Infill");
|
def->category = L("Infill");
|
||||||
|
|
@ -2398,6 +2402,18 @@ void PrintConfigDef::init_sla_params()
|
||||||
|
|
||||||
|
|
||||||
// SLA Material settings.
|
// 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 = this->add("initial_layer_height", coFloat);
|
||||||
def->label = L("Initial layer height");
|
def->label = L("Initial layer height");
|
||||||
def->tooltip = L("Initial layer height");
|
def->tooltip = L("Initial layer height");
|
||||||
|
|
@ -2475,6 +2491,10 @@ void PrintConfigDef::init_sla_params()
|
||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->set_default_value(new ConfigOptionString(""));
|
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 = this->add("default_sla_material_profile", coString);
|
||||||
def->label = L("Default SLA material profile");
|
def->label = L("Default SLA material profile");
|
||||||
def->tooltip = L("Default print profile associated with the current printer profile. "
|
def->tooltip = L("Default print profile associated with the current printer profile. "
|
||||||
|
|
@ -2694,6 +2714,17 @@ void PrintConfigDef::init_sla_params()
|
||||||
def->max = 30;
|
def->max = 30;
|
||||||
def->mode = comExpert;
|
def->mode = comExpert;
|
||||||
def->set_default_value(new ConfigOptionFloat(0.));
|
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 = this->add("pad_max_merge_distance", coFloat);
|
||||||
def->label = L("Max merge distance");
|
def->label = L("Max merge distance");
|
||||||
|
|
@ -2734,6 +2765,13 @@ void PrintConfigDef::init_sla_params()
|
||||||
def->tooltip = L("Create pad around object and ignore the support elevation");
|
def->tooltip = L("Create pad around object and ignore the support elevation");
|
||||||
def->mode = comSimple;
|
def->mode = comSimple;
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
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 = this->add("pad_object_gap", coFloat);
|
||||||
def->label = L("Pad object gap");
|
def->label = L("Pad object gap");
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,14 @@ enum FilamentType {
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
enum SLAMaterial {
|
||||||
|
slamTough,
|
||||||
|
slamFlex,
|
||||||
|
slamCasting,
|
||||||
|
slamDental,
|
||||||
|
slamHeatResistant,
|
||||||
|
};
|
||||||
|
|
||||||
enum SLADisplayOrientation {
|
enum SLADisplayOrientation {
|
||||||
sladoLandscape,
|
sladoLandscape,
|
||||||
sladoPortrait
|
sladoPortrait
|
||||||
|
|
@ -1022,6 +1030,9 @@ public:
|
||||||
|
|
||||||
// The height of the pad from the bottom to the top not considering the pit
|
// The height of the pad from the bottom to the top not considering the pit
|
||||||
ConfigOptionFloat pad_wall_height /*= 5*/;
|
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
|
// The greatest distance where two individual pads are merged into one. The
|
||||||
// distance is measured roughly from the centroids of the pads.
|
// distance is measured roughly from the centroids of the pads.
|
||||||
|
|
@ -1042,7 +1053,9 @@ public:
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Disable the elevation (ignore its value) and use the zero elevation mode
|
// 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
|
// This is the gap between the object bottom and the generated pad
|
||||||
ConfigOptionFloat pad_object_gap;
|
ConfigOptionFloat pad_object_gap;
|
||||||
|
|
@ -1082,10 +1095,12 @@ protected:
|
||||||
OPT_PTR(pad_enable);
|
OPT_PTR(pad_enable);
|
||||||
OPT_PTR(pad_wall_thickness);
|
OPT_PTR(pad_wall_thickness);
|
||||||
OPT_PTR(pad_wall_height);
|
OPT_PTR(pad_wall_height);
|
||||||
|
OPT_PTR(pad_brim_size);
|
||||||
OPT_PTR(pad_max_merge_distance);
|
OPT_PTR(pad_max_merge_distance);
|
||||||
// OPT_PTR(pad_edge_radius);
|
// OPT_PTR(pad_edge_radius);
|
||||||
OPT_PTR(pad_wall_slope);
|
OPT_PTR(pad_wall_slope);
|
||||||
OPT_PTR(pad_around_object);
|
OPT_PTR(pad_around_object);
|
||||||
|
OPT_PTR(pad_around_object_everywhere);
|
||||||
OPT_PTR(pad_object_gap);
|
OPT_PTR(pad_object_gap);
|
||||||
OPT_PTR(pad_object_connector_stride);
|
OPT_PTR(pad_object_connector_stride);
|
||||||
OPT_PTR(pad_object_connector_width);
|
OPT_PTR(pad_object_connector_width);
|
||||||
|
|
|
||||||
171
src/libslic3r/SLA/ConcaveHull.cpp
Normal file
171
src/libslic3r/SLA/ConcaveHull.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
#include "ConcaveHull.hpp"
|
||||||
|
#include <libslic3r/MTUtils.hpp>
|
||||||
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
|
#include "SLASpatIndex.hpp"
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
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<Point::coord_type>::max();
|
||||||
|
auto MIN = std::numeric_limits<Point::coord_type>::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<double>(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<double>(delta));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Points ConcaveHull::calculate_centroids() const
|
||||||
|
{
|
||||||
|
// We get the centroids of all the islands in the 2D slice
|
||||||
|
Points centroids = reserve_vector<Point>(m_polys.size());
|
||||||
|
std::transform(m_polys.begin(), m_polys.end(),
|
||||||
|
std::back_inserter(centroids),
|
||||||
|
[this](const Polygon &poly) { return centroid(poly); });
|
||||||
|
|
||||||
|
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<PointIndexEl> 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<float>(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<ExPolygon>(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
|
||||||
53
src/libslic3r/SLA/ConcaveHull.hpp
Normal file
53
src/libslic3r/SLA/ConcaveHull.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef CONCAVEHULL_HPP
|
||||||
|
#define CONCAVEHULL_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
|
||||||
|
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<void()>;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
/*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));
|
return 1./(2.4*get_required_density(angle));
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
|
||||||
const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn)
|
const std::vector<ExPolygons> &slices,
|
||||||
: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn)
|
const std::vector<float> & heights,
|
||||||
|
const Config & config,
|
||||||
|
std::function<void(void)> throw_on_cancel,
|
||||||
|
std::function<void(int)> statusfn)
|
||||||
|
: m_config(config)
|
||||||
|
, m_emesh(emesh)
|
||||||
|
, m_throw_on_cancel(throw_on_cancel)
|
||||||
|
, m_statusfn(statusfn)
|
||||||
{
|
{
|
||||||
process(slices, heights);
|
process(slices, heights);
|
||||||
project_onto_mesh(m_output);
|
project_onto_mesh(m_output);
|
||||||
|
|
@ -505,6 +513,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remove_bottom_points(std::vector<SupportPoint> &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
|
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||||
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
|
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
|
||||||
{
|
{
|
||||||
|
|
@ -533,4 +556,5 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::st
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,22 @@
|
||||||
// #define SLA_AUTOSUPPORTS_DEBUG
|
// #define SLA_AUTOSUPPORTS_DEBUG
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
class SLAAutoSupports {
|
class SLAAutoSupports {
|
||||||
public:
|
public:
|
||||||
struct Config {
|
struct Config {
|
||||||
float density_relative;
|
float density_relative {1.f};
|
||||||
float minimal_distance;
|
float minimal_distance {1.f};
|
||||||
float head_diameter;
|
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 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)
|
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<ExPolygons>& slices,
|
SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||||
|
|
||||||
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
||||||
|
|
||||||
struct MyLayer;
|
struct MyLayer;
|
||||||
|
|
@ -199,7 +201,9 @@ private:
|
||||||
std::function<void(int)> m_statusfn;
|
std::function<void(int)> m_statusfn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 <fstream>
|
|
||||||
// #include <libnest2d/tools/benchmark.h>
|
|
||||||
// #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<double>::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<double>(0.01);
|
|
||||||
Paths result;
|
|
||||||
offs.AddPath(ctour, jointype, etClosedPolygon);
|
|
||||||
offs.AddPaths(holes, jointype, etClosedPolygon);
|
|
||||||
offs.Execute(result, static_cast<double>(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<double>(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<size_t>(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<void(ClipperLib::PolyNode*, ExPolygon&)> 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<double>() - a.cast<double>();
|
|
||||||
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<coord_t>();
|
|
||||||
Point p2 = p1 + (sright * dirp).cast<coord_t>();
|
|
||||||
Point p3 = p2 + (sbottom * dir).cast<coord_t>();
|
|
||||||
Point p4 = p3 + (sright * -dirp).cast<coord_t>();
|
|
||||||
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<Point::coord_type>::max();
|
|
||||||
auto MIN = std::numeric_limits<Point::coord_type>::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<Point, unsigned>;
|
|
||||||
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<SpatElement> 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<float> &heights,
|
|
||||||
ThrowOnCancel thrfn)
|
|
||||||
{
|
|
||||||
if (mesh.empty()) return;
|
|
||||||
// m.require_shared_vertices(); // TriangleMeshSlicer needs this
|
|
||||||
TriangleMeshSlicer slicer(&mesh);
|
|
||||||
|
|
||||||
std::vector<ExPolygons> 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<double>(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<double>(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<float> 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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
#ifndef SLABASEPOOL_HPP
|
|
||||||
#define SLABASEPOOL_HPP
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <functional>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
class ExPolygon;
|
|
||||||
class Polygon;
|
|
||||||
using ExPolygons = std::vector<ExPolygon>;
|
|
||||||
using Polygons = std::vector<Polygon>;
|
|
||||||
|
|
||||||
class TriangleMesh;
|
|
||||||
|
|
||||||
namespace sla {
|
|
||||||
|
|
||||||
using ThrowOnCancel = std::function<void(void)>;
|
|
||||||
|
|
||||||
/// 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<float>&, // 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
|
|
||||||
|
|
@ -8,35 +8,19 @@
|
||||||
#include <libslic3r/ExPolygon.hpp>
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
#include <libslic3r/TriangleMesh.hpp>
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
|
||||||
|
#include "SLACommon.hpp"
|
||||||
|
#include "SLASpatIndex.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
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
|
/// Intermediate struct for a 3D mesh
|
||||||
struct Contour3D {
|
struct Contour3D {
|
||||||
Pointf3s points;
|
Pointf3s points;
|
||||||
std::vector<Vec3i> indices;
|
std::vector<Vec3i> indices;
|
||||||
|
|
||||||
void merge(const Contour3D& ctr) {
|
Contour3D& merge(const Contour3D& ctr)
|
||||||
|
{
|
||||||
auto s3 = coord_t(points.size());
|
auto s3 = coord_t(points.size());
|
||||||
auto s = indices.size();
|
auto s = indices.size();
|
||||||
|
|
||||||
|
|
@ -44,21 +28,27 @@ struct Contour3D {
|
||||||
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
||||||
|
|
||||||
for(size_t n = s; n < indices.size(); n++) {
|
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();
|
const size_t offs = points.size();
|
||||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||||
indices.reserve(indices.size() + points.size() / 3);
|
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);
|
indices.emplace_back(i, i + 1, i + 2);
|
||||||
|
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
// 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) {
|
for(auto& p : points) {
|
||||||
stream << "v " << p.transpose() << "\n";
|
stream << "v " << p.transpose() << "\n";
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +62,31 @@ struct Contour3D {
|
||||||
using ClusterEl = std::vector<unsigned>;
|
using ClusterEl = std::vector<unsigned>;
|
||||||
using ClusteredPoints = std::vector<ClusterEl>;
|
using ClusteredPoints = std::vector<ClusterEl>;
|
||||||
|
|
||||||
|
// Clustering a set of points by the given distance.
|
||||||
|
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points);
|
||||||
|
|
||||||
|
ClusteredPoints cluster(const PointSet& points,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points);
|
||||||
|
|
||||||
|
ClusteredPoints cluster(
|
||||||
|
const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||||
|
unsigned max_points);
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate the normals for the selected points (from 'points' set) on the
|
||||||
|
// mesh. This will call squared distance for each point.
|
||||||
|
PointSet normals(const PointSet& points,
|
||||||
|
const EigenMesh3D& mesh,
|
||||||
|
double eps = 0.05, // min distance from edges
|
||||||
|
std::function<void()> throw_on_cancel = [](){},
|
||||||
|
const std::vector<unsigned>& selected_points = {});
|
||||||
|
|
||||||
/// Mesh from an existing contour.
|
/// Mesh from an existing contour.
|
||||||
inline TriangleMesh mesh(const Contour3D& ctour) {
|
inline TriangleMesh mesh(const Contour3D& ctour) {
|
||||||
return {ctour.points, ctour.indices};
|
return {ctour.points, ctour.indices};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
#ifndef SLACOMMON_HPP
|
#ifndef SLACOMMON_HPP
|
||||||
#define SLACOMMON_HPP
|
#define SLACOMMON_HPP
|
||||||
|
|
||||||
#include <Eigen/Geometry>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <Eigen/Geometry>
|
||||||
|
|
||||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
|
|
||||||
|
|
@ -69,6 +70,8 @@ struct SupportPoint
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using SupportPoints = std::vector<SupportPoint>;
|
||||||
|
|
||||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||||
/// alternative (raw) input format for the SLASupportTree
|
/// alternative (raw) input format for the SLASupportTree
|
||||||
class EigenMesh3D {
|
class EigenMesh3D {
|
||||||
|
|
@ -175,6 +178,8 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using PointSet = Eigen::MatrixXd;
|
||||||
|
|
||||||
} // namespace sla
|
} // namespace sla
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
|
||||||
56
src/libslic3r/SLA/SLAConcurrency.hpp
Normal file
56
src/libslic3r/SLA/SLAConcurrency.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#ifndef SLACONCURRENCY_H
|
||||||
|
#define SLACONCURRENCY_H
|
||||||
|
|
||||||
|
#include <tbb/spin_mutex.h>
|
||||||
|
#include <tbb/mutex.h>
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
|
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<bool> struct _ccr {};
|
||||||
|
|
||||||
|
template<> struct _ccr<true>
|
||||||
|
{
|
||||||
|
using SpinningMutex = tbb::spin_mutex;
|
||||||
|
using BlockingMutex = tbb::mutex;
|
||||||
|
|
||||||
|
template<class It, class Fn>
|
||||||
|
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<false>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct _Mtx { inline void lock() {} inline void unlock() {} };
|
||||||
|
|
||||||
|
public:
|
||||||
|
using SpinningMutex = _Mtx;
|
||||||
|
using BlockingMutex = _Mtx;
|
||||||
|
|
||||||
|
template<class It, class Fn>
|
||||||
|
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<USE_FULL_CONCURRENCY>;
|
||||||
|
using ccr_seq = _ccr<false>;
|
||||||
|
using ccr_par = _ccr<true>;
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // SLACONCURRENCY_H
|
||||||
695
src/libslic3r/SLA/SLAPad.cpp
Normal file
695
src/libslic3r/SLA/SLAPad.cpp
Normal file
|
|
@ -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 <fstream>
|
||||||
|
// #include <libnest2d/tools/benchmark.h>
|
||||||
|
#include "SVG.hpp"
|
||||||
|
|
||||||
|
#include "I18N.hpp"
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
//! 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<double>::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<double>() - a.cast<double>();
|
||||||
|
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<coord_t>();
|
||||||
|
Point p2 = p1 + (sright * dirp).cast<coord_t>();
|
||||||
|
Point p3 = p2 + (sbottom * dir).cast<coord_t>();
|
||||||
|
Point p4 = p3 + (sright * -dirp).cast<coord_t>();
|
||||||
|
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<class...Args>
|
||||||
|
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<BoxIndexEl> 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 _Intersector>
|
||||||
|
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<float>(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<ExPolygon>(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<Intersector>;
|
||||||
|
using BrimPadSkeleton = _AroundPadSkeleton<DummyIntersector>;
|
||||||
|
|
||||||
|
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<class...Args>
|
||||||
|
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<float> &heights,
|
||||||
|
ThrowOnCancel thrfn)
|
||||||
|
{
|
||||||
|
if (mesh.empty()) return;
|
||||||
|
TriangleMeshSlicer slicer(&mesh);
|
||||||
|
|
||||||
|
auto out = reserve_vector<ExPolygons>(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<ExPolygon>(count);
|
||||||
|
for(ExPolygons& o : out)
|
||||||
|
for(ExPolygon& e : o) {
|
||||||
|
auto&& exss = e.simplify(scaled<double>(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<double>(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<float> 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
|
||||||
94
src/libslic3r/SLA/SLAPad.hpp
Normal file
94
src/libslic3r/SLA/SLAPad.hpp
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#ifndef SLABASEPOOL_HPP
|
||||||
|
#define SLABASEPOOL_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class ExPolygon;
|
||||||
|
class Polygon;
|
||||||
|
using ExPolygons = std::vector<ExPolygon>;
|
||||||
|
using Polygons = std::vector<Polygon>;
|
||||||
|
|
||||||
|
class TriangleMesh;
|
||||||
|
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
|
using ThrowOnCancel = std::function<void(void)>;
|
||||||
|
|
||||||
|
/// Calculate the polygon representing the silhouette.
|
||||||
|
void pad_blueprint(
|
||||||
|
const TriangleMesh &mesh, // input mesh
|
||||||
|
ExPolygons & output, // Output will be merged with
|
||||||
|
const std::vector<float> &, // 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
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "SLARaster.hpp"
|
#include "SLARaster.hpp"
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
|
#include "libslic3r/MTUtils.hpp"
|
||||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||||
|
|
||||||
// For rasterizing
|
// For rasterizing
|
||||||
|
|
@ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H
|
||||||
|
|
||||||
namespace sla {
|
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<TPixelRenderer>;
|
||||||
|
using TPixel = TPixelRenderer::color_type;
|
||||||
|
using TRawBuffer = agg::rendering_buffer;
|
||||||
|
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
|
||||||
|
|
||||||
|
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
|
||||||
|
|
||||||
class Raster::Impl {
|
class Raster::Impl {
|
||||||
public:
|
public:
|
||||||
using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
|
|
||||||
using TRawRenderer = agg::renderer_base<TPixelRenderer>;
|
|
||||||
using TPixel = TPixelRenderer::color_type;
|
|
||||||
using TRawBuffer = agg::rendering_buffer;
|
|
||||||
|
|
||||||
using TBuffer = std::vector<TPixelRenderer::pixel_type>;
|
|
||||||
|
|
||||||
using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
|
|
||||||
|
|
||||||
static const TPixel ColorWhite;
|
static const TPixel ColorWhite;
|
||||||
static const TPixel ColorBlack;
|
static const TPixel ColorBlack;
|
||||||
|
|
||||||
using Format = Raster::Format;
|
using Format = Raster::RawData;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Raster::Resolution m_resolution;
|
Raster::Resolution m_resolution;
|
||||||
// Raster::PixelDim m_pxdim;
|
|
||||||
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
|
||||||
TBuffer m_buf;
|
TBuffer m_buf;
|
||||||
TRawBuffer m_rbuf;
|
TRawBuffer m_rbuf;
|
||||||
|
|
@ -59,74 +65,49 @@ private:
|
||||||
TRendererAA m_renderer;
|
TRendererAA m_renderer;
|
||||||
|
|
||||||
std::function<double(double)> m_gammafn;
|
std::function<double(double)> m_gammafn;
|
||||||
std::array<bool, 2> m_mirror;
|
Trafo m_trafo;
|
||||||
Format m_fmt = Format::PNG;
|
|
||||||
|
|
||||||
inline void flipy(agg::path_storage& path) const {
|
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 {
|
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:
|
public:
|
||||||
|
inline Impl(const Raster::Resolution & res,
|
||||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
const Raster::PixelDim & pd,
|
||||||
const std::array<bool, 2>& mirror, double gamma = 1.0):
|
const Trafo &trafo)
|
||||||
m_resolution(res),
|
: m_resolution(res)
|
||||||
// m_pxdim(pd),
|
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
|
||||||
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
|
, m_buf(res.pixels())
|
||||||
m_buf(res.pixels()),
|
, m_rbuf(reinterpret_cast<TPixelRenderer::value_type *>(m_buf.data()),
|
||||||
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
|
unsigned(res.width_px),
|
||||||
res.width_px, res.height_px,
|
unsigned(res.height_px),
|
||||||
int(res.width_px*TPixelRenderer::num_components)),
|
int(res.width_px * TPixelRenderer::num_components))
|
||||||
m_pixfmt(m_rbuf),
|
, m_pixfmt(m_rbuf)
|
||||||
m_raw_renderer(m_pixfmt),
|
, m_raw_renderer(m_pixfmt)
|
||||||
m_renderer(m_raw_renderer),
|
, m_renderer(m_raw_renderer)
|
||||||
m_mirror(mirror)
|
, m_trafo(trafo)
|
||||||
{
|
{
|
||||||
m_renderer.color(ColorWhite);
|
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);
|
else m_gammafn = agg::gamma_threshold(0.5);
|
||||||
|
|
||||||
clear();
|
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<class P> void draw(const P &poly) {
|
template<class P> void draw(const P &poly) {
|
||||||
agg::rasterizer_scanline_aa<> ras;
|
agg::rasterizer_scanline_aa<> ras;
|
||||||
agg::scanline_p8 scanlines;
|
agg::scanline_p8 scanlines;
|
||||||
|
|
||||||
ras.gamma(m_gammafn);
|
ras.gamma(m_gammafn);
|
||||||
|
|
||||||
auto&& path = to_path(contour(poly));
|
|
||||||
|
|
||||||
if(m_mirror[X]) flipx(path);
|
ras.add_path(to_path(contour(poly)));
|
||||||
if(m_mirror[Y]) flipy(path);
|
for(auto& h : holes(poly)) ras.add_path(to_path(h));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
agg::render_scanlines(ras, scanlines, m_renderer);
|
agg::render_scanlines(ras, scanlines, m_renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,11 +116,16 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
inline TBuffer& buffer() { return m_buf; }
|
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::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:
|
private:
|
||||||
inline double getPx(const Point& p) {
|
inline double getPx(const Point& p) {
|
||||||
return p(0) * m_pxdim_scaled.w_mm;
|
return p(0) * m_pxdim_scaled.w_mm;
|
||||||
|
|
@ -162,49 +148,67 @@ private:
|
||||||
return p.Y * m_pxdim_scaled.h_mm;
|
return p.Y * m_pxdim_scaled.h_mm;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class PointVec> agg::path_storage to_path(const PointVec& poly)
|
template<class PointVec> agg::path_storage _to_path(const PointVec& v)
|
||||||
{
|
{
|
||||||
agg::path_storage path;
|
agg::path_storage path;
|
||||||
|
|
||||||
auto it = poly.begin();
|
auto it = v.begin();
|
||||||
path.move_to(getPx(*it), getPy(*it));
|
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<class PointVec> 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<class PointVec> 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;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
|
const TPixel Raster::Impl::ColorWhite = TPixel(255);
|
||||||
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
|
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() = default;
|
||||||
|
|
||||||
// Raster::Raster(Raster &&m) = default;
|
Raster::Raster(Raster &&m) = default;
|
||||||
// Raster& Raster::operator=(Raster&&) = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
||||||
Format fmt, double gamma)
|
const Trafo &trafo)
|
||||||
{
|
{
|
||||||
m_impl.reset();
|
m_impl.reset();
|
||||||
m_impl.reset(new Impl(r, pd, fmt, gamma));
|
m_impl.reset(new Impl(r, pd, trafo));
|
||||||
}
|
|
||||||
|
|
||||||
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
|
|
||||||
const std::array<bool, 2>& mirror, double gamma)
|
|
||||||
{
|
|
||||||
m_impl.reset();
|
|
||||||
m_impl.reset(new Impl(r, pd, mirror, gamma));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Raster::reset()
|
void Raster::reset()
|
||||||
|
|
@ -214,9 +218,16 @@ void Raster::reset()
|
||||||
|
|
||||||
Raster::Resolution Raster::resolution() const
|
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()
|
void Raster::clear()
|
||||||
|
|
@ -227,103 +238,83 @@ void Raster::clear()
|
||||||
|
|
||||||
void Raster::draw(const ExPolygon &expoly)
|
void Raster::draw(const ExPolygon &expoly)
|
||||||
{
|
{
|
||||||
|
assert(m_impl);
|
||||||
m_impl->draw(expoly);
|
m_impl->draw(expoly);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Raster::draw(const ClipperLib::Polygon &poly)
|
void Raster::draw(const ClipperLib::Polygon &poly)
|
||||||
{
|
{
|
||||||
|
assert(m_impl);
|
||||||
m_impl->draw(poly);
|
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);
|
assert (m_impl);
|
||||||
if(!stream.good()) return;
|
TPixel::value_type px;
|
||||||
|
m_impl->buffer()[y * resolution().width_px + x].get(px);
|
||||||
switch(fmt) {
|
return px;
|
||||||
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<const char*>(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<const char*>(m_impl->buffer().data()),
|
|
||||||
std::streamsize(sz));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<std::uint8_t*>(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);
|
stream.write(reinterpret_cast<const char *>(bytes.data()),
|
||||||
|
std::streamsize(bytes.size()));
|
||||||
std::vector<std::uint8_t> data; size_t s = 0;
|
|
||||||
|
return stream;
|
||||||
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<std::uint8_t*>(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<std::uint8_t*>(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)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<const std::uint8_t*>(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
|
#endif // SLARASTER_CPP
|
||||||
|
|
|
||||||
|
|
@ -8,45 +8,13 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
|
||||||
namespace ClipperLib { struct Polygon; }
|
namespace ClipperLib { struct Polygon; }
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ExPolygon;
|
|
||||||
|
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
// Raw byte buffer paired with its size. Suitable for compressed PNG data.
|
|
||||||
class RawBytes {
|
|
||||||
|
|
||||||
std::vector<std::uint8_t> m_buffer;
|
|
||||||
public:
|
|
||||||
|
|
||||||
RawBytes() = default;
|
|
||||||
RawBytes(std::vector<std::uint8_t>&& 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
|
* @brief Raster captures an anti-aliased monochrome canvas where vectorial
|
||||||
* polygons can be rasterized. Fill color is always white and the background is
|
* polygons can be rasterized. Fill color is always white and the background is
|
||||||
|
|
@ -60,10 +28,28 @@ class Raster {
|
||||||
std::unique_ptr<Impl> m_impl;
|
std::unique_ptr<Impl> m_impl;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// Supported compression types
|
// Raw byte buffer paired with its size. Suitable for compressed image data.
|
||||||
enum class Format {
|
class RawData
|
||||||
RAW, //!> Uncompressed pixel data
|
{
|
||||||
PNG //!> PNG compression
|
protected:
|
||||||
|
std::vector<std::uint8_t> m_buffer;
|
||||||
|
const Impl& get_internals(const Raster& raster);
|
||||||
|
public:
|
||||||
|
RawData() = default;
|
||||||
|
RawData(std::vector<std::uint8_t>&& 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.
|
/// Type that represents a resolution in pixels.
|
||||||
|
|
@ -86,11 +72,36 @@ public:
|
||||||
w_mm(px_width_mm), h_mm(px_height_mm) {}
|
w_mm(px_width_mm), h_mm(px_height_mm) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Constructor taking the resolution and the pixel dimension.
|
enum Orientation { roLandscape, roPortrait };
|
||||||
template <class...Args> Raster(Args...args) {
|
|
||||||
reset(std::forward<Args>(args)...);
|
using TMirroring = std::array<bool, 2>;
|
||||||
}
|
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(const Raster& cpy) = delete;
|
||||||
Raster& operator=(const Raster& cpy) = delete;
|
Raster& operator=(const Raster& cpy) = delete;
|
||||||
Raster(Raster&& m);
|
Raster(Raster&& m);
|
||||||
|
|
@ -98,18 +109,10 @@ public:
|
||||||
~Raster();
|
~Raster();
|
||||||
|
|
||||||
/// Reallocated everything for the given resolution and pixel dimension.
|
/// Reallocated everything for the given resolution and pixel dimension.
|
||||||
/// The third parameter is either the X, Y mirroring or a supported format
|
void reset(const Resolution& r,
|
||||||
/// for which the correct mirroring will be configured.
|
const PixelDim& pd,
|
||||||
void reset(const Resolution&,
|
const Trafo &tr = {});
|
||||||
const PixelDim&,
|
|
||||||
const std::array<bool, 2>& mirror,
|
|
||||||
double gamma = 1.0);
|
|
||||||
|
|
||||||
void reset(const Resolution& r,
|
|
||||||
const PixelDim& pd,
|
|
||||||
Format o,
|
|
||||||
double gamma = 1.0);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the allocated resources. Drawing in this state ends in
|
* Release the allocated resources. Drawing in this state ends in
|
||||||
* unspecified behavior.
|
* unspecified behavior.
|
||||||
|
|
@ -118,6 +121,7 @@ public:
|
||||||
|
|
||||||
/// Get the resolution of the raster.
|
/// Get the resolution of the raster.
|
||||||
Resolution resolution() const;
|
Resolution resolution() const;
|
||||||
|
PixelDim pixel_dimensions() const;
|
||||||
|
|
||||||
/// Clear the raster with black color.
|
/// Clear the raster with black color.
|
||||||
void clear();
|
void clear();
|
||||||
|
|
@ -126,24 +130,28 @@ public:
|
||||||
void draw(const ExPolygon& poly);
|
void draw(const ExPolygon& poly);
|
||||||
void draw(const ClipperLib::Polygon& poly);
|
void draw(const ClipperLib::Polygon& poly);
|
||||||
|
|
||||||
// Saving the raster:
|
uint8_t read_pixel(size_t w, size_t h) const;
|
||||||
// It is possible to override the format given in the constructor but
|
|
||||||
// be aware that the mirroring will not be modified.
|
inline bool empty() const { return ! bool(m_impl); }
|
||||||
|
|
||||||
/// Save the raster on the specified stream.
|
|
||||||
void save(std::ostream& stream, Format);
|
|
||||||
void save(std::ostream& stream);
|
|
||||||
|
|
||||||
/// Save into a continuous byte stream which is returned.
|
|
||||||
RawBytes save(Format fmt);
|
|
||||||
RawBytes save();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// This prevents the duplicate default constructor warning on MSVC2013
|
class PNGImage: public Raster::RawData {
|
||||||
template<> Raster::Raster();
|
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
|
} // sla
|
||||||
} // Slic3r
|
} // Slic3r
|
||||||
|
|
||||||
|
|
||||||
#endif // SLARASTER_HPP
|
#endif // SLARASTER_HPP
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
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 = ");
|
std::string out("action = print\njobDir = ");
|
||||||
out += projectname + "\n";
|
out += projectname + "\n";
|
||||||
|
|
@ -21,39 +21,14 @@ std::string SLARasterWriter::createIniContent(const std::string& projectname) co
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLARasterWriter::flpXY(ClipperLib::Polygon &poly)
|
RasterWriter::RasterWriter(const Raster::Resolution &res,
|
||||||
{
|
const Raster::PixelDim & pixdim,
|
||||||
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
|
const Raster::Trafo & trafo,
|
||||||
std::reverse(poly.Contour.begin(), poly.Contour.end());
|
double gamma)
|
||||||
|
: m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma)
|
||||||
for(auto& h : poly.Holes) {
|
{}
|
||||||
for(auto& p : h) std::swap(p.X, p.Y);
|
|
||||||
std::reverse(h.begin(), h.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SLARasterWriter::flpXY(ExPolygon &poly)
|
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
||||||
{
|
|
||||||
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<bool, 2> &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)
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Zipper zipper(fpath); // zipper with no compression
|
Zipper zipper(fpath); // zipper with no compression
|
||||||
|
|
@ -103,7 +78,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
||||||
|
|
||||||
} // namespace
|
} // 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["layerHeight"] = get_cfg_value(cfg, "layer_height");
|
||||||
m_config["expTime"] = get_cfg_value(cfg, "exposure_time");
|
m_config["expTime"] = get_cfg_value(cfg, "exposure_time");
|
||||||
|
|
@ -114,11 +89,11 @@ void SLARasterWriter::set_config(const DynamicPrintConfig &cfg)
|
||||||
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
||||||
m_config["printProfile"] = get_cfg_value(cfg, "sla_print_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;
|
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["usedMaterial"] = std::to_string(stats.used_material);
|
||||||
m_config["numFade"] = std::to_string(stats.num_fade);
|
m_config["numFade"] = std::to_string(stats.num_fade);
|
||||||
|
|
|
||||||
|
|
@ -15,20 +15,17 @@
|
||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
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
|
// Be aware that if a large number of layers are allocated, it can very well
|
||||||
// exhaust the available memory especially on 32 bit platform.
|
// exhaust the available memory especially on 32 bit platform.
|
||||||
// This class is designed to be used in parallel mode. Layers have an ID and
|
// 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).
|
// 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
|
// At the end when all layers where written, the save method can be used to
|
||||||
// write out the result into a zipped archive.
|
// write out the result into a zipped archive.
|
||||||
class SLARasterWriter
|
class RasterWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Orientation {
|
|
||||||
roLandscape,
|
|
||||||
roPortrait
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used for addressing parameters of set_statistics()
|
// Used for addressing parameters of set_statistics()
|
||||||
struct PrintStatistics
|
struct PrintStatistics
|
||||||
|
|
@ -45,7 +42,7 @@ private:
|
||||||
// A struct to bind the raster image data and its compressed bytes together.
|
// A struct to bind the raster image data and its compressed bytes together.
|
||||||
struct Layer {
|
struct Layer {
|
||||||
Raster raster;
|
Raster raster;
|
||||||
RawBytes rawbytes;
|
PNGImage rawbytes;
|
||||||
|
|
||||||
Layer() = default;
|
Layer() = default;
|
||||||
|
|
||||||
|
|
@ -61,70 +58,55 @@ private:
|
||||||
// parallel. Later we can write every layer to the disk sequentially.
|
// parallel. Later we can write every layer to the disk sequentially.
|
||||||
std::vector<Layer> m_layers_rst;
|
std::vector<Layer> m_layers_rst;
|
||||||
Raster::Resolution m_res;
|
Raster::Resolution m_res;
|
||||||
Raster::PixelDim m_pxdim;
|
Raster::PixelDim m_pxdim;
|
||||||
std::array<bool, 2> m_mirror;
|
Raster::Trafo m_trafo;
|
||||||
double m_gamma;
|
double m_gamma;
|
||||||
|
|
||||||
std::map<std::string, std::string> m_config;
|
std::map<std::string, std::string> m_config;
|
||||||
|
|
||||||
std::string createIniContent(const std::string& projectname) const;
|
std::string createIniContent(const std::string& projectname) const;
|
||||||
|
|
||||||
static void flpXY(ClipperLib::Polygon& poly);
|
|
||||||
static void flpXY(ExPolygon& poly);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SLARasterWriter(const Raster::Resolution &res,
|
|
||||||
const Raster::PixelDim &pixdim,
|
// SLARasterWriter is using Raster in custom mirroring mode
|
||||||
const std::array<bool, 2> &mirror,
|
RasterWriter(const Raster::Resolution &res,
|
||||||
double gamma = 1.);
|
const Raster::PixelDim & pixdim,
|
||||||
|
const Raster::Trafo & trafo,
|
||||||
|
double gamma = 1.);
|
||||||
|
|
||||||
SLARasterWriter(const SLARasterWriter& ) = delete;
|
RasterWriter(const RasterWriter& ) = delete;
|
||||||
SLARasterWriter& operator=(const SLARasterWriter&) = delete;
|
RasterWriter& operator=(const RasterWriter&) = delete;
|
||||||
SLARasterWriter(SLARasterWriter&& m) = default;
|
RasterWriter(RasterWriter&& m) = default;
|
||||||
SLARasterWriter& operator=(SLARasterWriter&&) = default;
|
RasterWriter& operator=(RasterWriter&&) = default;
|
||||||
|
|
||||||
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
|
||||||
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
|
||||||
|
|
||||||
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr,
|
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr)
|
||||||
Orientation o = roPortrait)
|
|
||||||
{
|
{
|
||||||
assert(lyr < m_layers_rst.size());
|
assert(lyr < m_layers_rst.size());
|
||||||
|
m_layers_rst[lyr].raster.draw(p);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void begin_layer(unsigned lyr) {
|
inline void begin_layer(unsigned lyr) {
|
||||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
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() {
|
inline void begin_layer() {
|
||||||
m_layers_rst.emplace_back();
|
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) {
|
inline void finish_layer(unsigned lyr_id) {
|
||||||
assert(lyr_id < m_layers_rst.size());
|
assert(lyr_id < m_layers_rst.size());
|
||||||
m_layers_rst[lyr_id].rawbytes =
|
m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster);
|
||||||
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
|
|
||||||
m_layers_rst[lyr_id].raster.reset();
|
m_layers_rst[lyr_id].raster.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void finish_layer() {
|
inline void finish_layer() {
|
||||||
if(!m_layers_rst.empty()) {
|
if(!m_layers_rst.empty()) {
|
||||||
m_layers_rst.back().rawbytes =
|
m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster);
|
||||||
m_layers_rst.back().raster.save(Raster::Format::PNG);
|
|
||||||
m_layers_rst.back().raster.reset();
|
m_layers_rst.back().raster.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,19 @@ public:
|
||||||
insert(std::make_pair(v, unsigned(idx)));
|
insert(std::make_pair(v, unsigned(idx)));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>);
|
std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>) const;
|
||||||
std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k);
|
std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k) const;
|
||||||
|
std::vector<PointIndexEl> query(const Vec3d &v, unsigned k) const // wrapper
|
||||||
|
{
|
||||||
|
return nearest(v, k);
|
||||||
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
bool empty() const { return size() == 0; }
|
bool empty() const { return size() == 0; }
|
||||||
|
|
||||||
void foreach(std::function<void(const PointIndexEl& el)> fn);
|
void foreach(std::function<void(const PointIndexEl& el)> fn);
|
||||||
|
void foreach(std::function<void(const PointIndexEl& el)> fn) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>;
|
using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,24 +2,14 @@
|
||||||
#define SLASUPPORTTREE_HPP
|
#define SLASUPPORTTREE_HPP
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <Eigen/Geometry>
|
#include <Eigen/Geometry>
|
||||||
|
|
||||||
#include "SLACommon.hpp"
|
#include "SLACommon.hpp"
|
||||||
|
#include "SLAPad.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
// Needed types from Point.hpp
|
|
||||||
typedef int32_t coord_t;
|
|
||||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
|
||||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
|
||||||
typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd;
|
|
||||||
typedef std::vector<Vec3d> Pointf3s;
|
|
||||||
typedef std::vector<Vec3crd> Points3;
|
|
||||||
|
|
||||||
class TriangleMesh;
|
class TriangleMesh;
|
||||||
class Model;
|
class Model;
|
||||||
class ModelInstance;
|
class ModelInstance;
|
||||||
|
|
@ -32,13 +22,17 @@ using ExPolygons = std::vector<ExPolygon>;
|
||||||
|
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
enum class PillarConnectionMode {
|
enum class PillarConnectionMode
|
||||||
|
{
|
||||||
zigzag,
|
zigzag,
|
||||||
cross,
|
cross,
|
||||||
dynamic
|
dynamic
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SupportConfig {
|
struct SupportConfig
|
||||||
|
{
|
||||||
|
bool enabled = true;
|
||||||
|
|
||||||
// Radius in mm of the pointing side of the head.
|
// Radius in mm of the pointing side of the head.
|
||||||
double head_front_radius_mm = 0.2;
|
double head_front_radius_mm = 0.2;
|
||||||
|
|
||||||
|
|
@ -85,6 +79,11 @@ struct SupportConfig {
|
||||||
// The shortest distance between a pillar base perimeter from the model
|
// The shortest distance between a pillar base perimeter from the model
|
||||||
// body. This is only useful when elevation is set to zero.
|
// body. This is only useful when elevation is set to zero.
|
||||||
double pillar_base_safety_distance_mm = 0.5;
|
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)
|
// Compile time configuration values (candidates for runtime)
|
||||||
|
|
@ -104,101 +103,78 @@ struct SupportConfig {
|
||||||
static const unsigned max_bridges_on_pillar;
|
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
|
/// A Control structure for the support calculation. Consists of the status
|
||||||
/// indicator callback and the stop condition predicate.
|
/// indicator callback and the stop condition predicate.
|
||||||
struct Controller {
|
struct JobController
|
||||||
|
{
|
||||||
|
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||||
|
using StopCond = std::function<bool(void)>;
|
||||||
|
using CancelFn = std::function<void(void)>;
|
||||||
|
|
||||||
// This will signal the status of the calculation to the front-end
|
// This will signal the status of the calculation to the front-end
|
||||||
std::function<void(unsigned, const std::string&)> statuscb =
|
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||||
[](unsigned, const std::string&){};
|
|
||||||
|
|
||||||
// Returns true if the calculation should be aborted.
|
// Returns true if the calculation should be aborted.
|
||||||
std::function<bool(void)> stopcondition = [](){ return false; };
|
StopCond stopcondition = [](){ return false; };
|
||||||
|
|
||||||
// Similar to cancel callback. This should check the stop condition and
|
// Similar to cancel callback. This should check the stop condition and
|
||||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||||
// terminate itself
|
// terminate itself
|
||||||
std::function<void(void)> cancelfn = [](){};
|
CancelFn cancelfn = [](){};
|
||||||
};
|
};
|
||||||
|
|
||||||
using PointSet = Eigen::MatrixXd;
|
struct SupportableMesh
|
||||||
|
{
|
||||||
|
EigenMesh3D emesh;
|
||||||
|
SupportPoints pts;
|
||||||
|
SupportConfig cfg;
|
||||||
|
|
||||||
//EigenMesh3D to_eigenmesh(const TriangleMesh& m);
|
explicit SupportableMesh(const TriangleMesh & trmsh,
|
||||||
|
const SupportPoints &sp,
|
||||||
// needed for find best rotation
|
const SupportConfig &c)
|
||||||
//EigenMesh3D to_eigenmesh(const ModelObject& model);
|
: emesh{trmsh}, pts{sp}, cfg{c}
|
||||||
|
{}
|
||||||
// Simple conversion of 'vector of points' to an Eigen matrix
|
|
||||||
//PointSet to_point_set(const std::vector<sla::SupportPoint>&);
|
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.
|
/// The class containing mesh data for the generated supports.
|
||||||
class SLASupportTree {
|
class SupportTree
|
||||||
class Impl; // persistent support data
|
{
|
||||||
std::unique_ptr<Impl> m_impl;
|
JobController m_ctl;
|
||||||
|
|
||||||
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<SupportPoint>& pts,
|
|
||||||
const EigenMesh3D& mesh,
|
|
||||||
const SupportConfig& cfg = {},
|
|
||||||
const Controller& ctl = {});
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using UPtr = std::unique_ptr<SupportTree>;
|
||||||
SLASupportTree(double ground_level = 0.0);
|
|
||||||
|
|
||||||
SLASupportTree(const std::vector<SupportPoint>& pts,
|
|
||||||
const EigenMesh3D& em,
|
|
||||||
const SupportConfig& cfg = {},
|
|
||||||
const Controller& ctl = {});
|
|
||||||
|
|
||||||
SLASupportTree(const SLASupportTree&) = delete;
|
static UPtr create(const SupportableMesh &input,
|
||||||
SLASupportTree& operator=(const SLASupportTree&) = delete;
|
const JobController &ctl = {});
|
||||||
|
|
||||||
~SLASupportTree();
|
virtual ~SupportTree() = default;
|
||||||
|
|
||||||
/// Get the whole mesh united into the output TriangleMesh
|
virtual const TriangleMesh &retrieve_mesh(MeshType meshtype) const = 0;
|
||||||
/// WITHOUT THE PAD
|
|
||||||
const TriangleMesh& merged_mesh() const;
|
|
||||||
|
|
||||||
void merged_mesh_with_pad(TriangleMesh&) const;
|
/// Adding the "pad" under the supports.
|
||||||
|
|
||||||
std::vector<ExPolygons> slice(const std::vector<float> &,
|
|
||||||
float closing_radius) const;
|
|
||||||
|
|
||||||
/// Adding the "pad" (base pool) under the supports
|
|
||||||
/// modelbase will be used according to the embed_object flag in PoolConfig.
|
/// 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
|
/// Otherwise, the modelbase will be unified with the base plate calculated
|
||||||
/// from the supports.
|
/// from the supports.
|
||||||
const TriangleMesh& add_pad(const ExPolygons& modelbase,
|
virtual const TriangleMesh &add_pad(const ExPolygons &modelbase,
|
||||||
const PoolConfig& pcfg) const;
|
const PadConfig & pcfg) = 0;
|
||||||
|
|
||||||
/// Get the pad geometry
|
virtual void remove_pad() = 0;
|
||||||
const TriangleMesh& get_pad() const;
|
|
||||||
|
std::vector<ExPolygons> slice(const std::vector<float> &,
|
||||||
void remove_pad();
|
float closing_radius) const;
|
||||||
|
|
||||||
|
void retrieve_full_mesh(TriangleMesh &outmesh) const;
|
||||||
|
|
||||||
|
const JobController &ctl() const { return m_ctl; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
525
src/libslic3r/SLA/SLASupportTreeBuilder.cpp
Normal file
525
src/libslic3r/SLA/SLASupportTreeBuilder.cpp
Normal file
|
|
@ -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<double> 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<double>;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
496
src/libslic3r/SLA/SLASupportTreeBuilder.hpp
Normal file
496
src/libslic3r/SLA/SLASupportTreeBuilder.hpp
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
#ifndef SUPPORTTREEBUILDER_HPP
|
||||||
|
#define SUPPORTTREEBUILDER_HPP
|
||||||
|
|
||||||
|
#include "SLAConcurrency.hpp"
|
||||||
|
#include "SLABoilerPlate.hpp"
|
||||||
|
#include "SLASupportTree.hpp"
|
||||||
|
#include "SLAPad.hpp"
|
||||||
|
#include <libslic3r/MTUtils.hpp>
|
||||||
|
|
||||||
|
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<double, double>;
|
||||||
|
|
||||||
|
inline Portion make_portion(double a, double b) {
|
||||||
|
return std::make_tuple(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Vec> double distance(const Vec& p) {
|
||||||
|
return std::sqrt(p.transpose() * p);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Vec> 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<double>;
|
||||||
|
|
||||||
|
// 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<Head> m_heads;
|
||||||
|
std::vector<size_t> m_head_indices;
|
||||||
|
std::vector<Pillar> m_pillars;
|
||||||
|
std::vector<Junction> m_junctions;
|
||||||
|
std::vector<Bridge> m_bridges;
|
||||||
|
std::vector<Bridge> m_crossbridges;
|
||||||
|
std::vector<CompactBridge> 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<class...Args>
|
||||||
|
const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
br.emplace_back(std::forward<Args>(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<class...Args> Head& add_head(unsigned id, Args&&... args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
m_heads.emplace_back(std::forward<Args>(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<class...Args> long add_pillar(long headid, Args&&... args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> 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>(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<Mutex> 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<Mutex> 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<Mutex> 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<Mutex> lk(m_mutex);
|
||||||
|
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
|
||||||
|
return pillar.bridges;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class...Args> long add_pillar(Args&&...args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
if (m_pillars.capacity() < m_heads.size())
|
||||||
|
m_pillars.reserve(m_heads.size() * 10);
|
||||||
|
|
||||||
|
m_pillars.emplace_back(std::forward<Args>(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<Mutex> 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<class...Args> const Junction& add_junction(Args&&... args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
m_junctions.emplace_back(std::forward<Args>(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<Mutex> 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<class...Args> const Bridge& add_crossbridge(Args&&... args)
|
||||||
|
{
|
||||||
|
return _add_bridge(m_crossbridges, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
m_compact_bridges.emplace_back(std::forward<Args>(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<Mutex> 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<Mutex> lk(m_mutex);
|
||||||
|
return m_pillars.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::vector<Pillar> &pillars() const { return m_pillars; }
|
||||||
|
inline const std::vector<Head> &heads() const { return m_heads; }
|
||||||
|
inline const std::vector<Bridge> &bridges() const { return m_bridges; }
|
||||||
|
inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; }
|
||||||
|
|
||||||
|
template<class T> inline IntegerOnly<T, const Pillar&> pillar(T id) const
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
assert(id >= 0 && size_t(id) < m_pillars.size() &&
|
||||||
|
size_t(id) < std::numeric_limits<size_t>::max());
|
||||||
|
|
||||||
|
return m_pillars[size_t(id)];
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> inline IntegerOnly<T, Pillar&> pillar(T id)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lk(m_mutex);
|
||||||
|
assert(id >= 0 && size_t(id) < m_pillars.size() &&
|
||||||
|
size_t(id) < std::numeric_limits<size_t>::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
|
||||||
1387
src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp
Normal file
1387
src/libslic3r/SLA/SLASupportTreeBuildsteps.cpp
Normal file
File diff suppressed because it is too large
Load diff
289
src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp
Normal file
289
src/libslic3r/SLA/SLASupportTreeBuildsteps.hpp
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
#ifndef SLASUPPORTTREEALGORITHM_H
|
||||||
|
#define SLASUPPORTTREEALGORITHM_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#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<class DistFn>
|
||||||
|
long cluster_centroid(const ClusterEl& clust,
|
||||||
|
const std::function<Vec3d(size_t)> &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<bool> sel(clust.size(), false); // create full zero bitmask
|
||||||
|
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
||||||
|
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
||||||
|
|
||||||
|
do {
|
||||||
|
std::array<size_t, 2> 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<class...Args> inline void guarded_insert(Args&&...args)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
|
m_index.insert(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class...Args>
|
||||||
|
inline std::vector<PointIndexEl> guarded_query(Args&&...args) const
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
|
return m_index.query(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class...Args> inline void insert(Args&&...args)
|
||||||
|
{
|
||||||
|
m_index.insert(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class...Args>
|
||||||
|
inline std::vector<PointIndexEl> query(Args&&...args) const
|
||||||
|
{
|
||||||
|
return m_index.query(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Fn> inline void foreach(Fn fn) { m_index.foreach(fn); }
|
||||||
|
template<class Fn> inline void guarded_foreach(Fn fn)
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lck(m_mutex);
|
||||||
|
m_index.foreach(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
PointIndex guarded_clone()
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> 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<class I, class DoubleI = IntegerOnly<I>>
|
||||||
|
IntegerOnly<DoubleI> 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<SupportPoint>& m_support_pts;
|
||||||
|
|
||||||
|
using PtIndices = std::vector<unsigned>;
|
||||||
|
|
||||||
|
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<std::pair<unsigned, EigenMesh3D::hit_result>> 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<PtIndices> 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
|
||||||
|
|
@ -77,7 +77,7 @@ bool PointIndex::remove(const PointIndexEl& el)
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PointIndexEl>
|
std::vector<PointIndexEl>
|
||||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn)
|
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||||
{
|
{
|
||||||
namespace bgi = boost::geometry::index;
|
namespace bgi = boost::geometry::index;
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ PointIndex::query(std::function<bool(const PointIndexEl &)> fn)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1)
|
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||||
{
|
{
|
||||||
namespace bgi = boost::geometry::index;
|
namespace bgi = boost::geometry::index;
|
||||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||||
|
|
@ -104,6 +104,11 @@ void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||||
for(auto& el : m_impl->m_store) fn(el);
|
for(auto& el : m_impl->m_store) fn(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||||
|
{
|
||||||
|
for(const auto &el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
/* **************************************************************************
|
/* **************************************************************************
|
||||||
* BoxIndex implementation
|
* BoxIndex implementation
|
||||||
* ************************************************************************** */
|
* ************************************************************************** */
|
||||||
|
|
@ -274,6 +279,8 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||||
* Misc functions
|
* Misc functions
|
||||||
* ****************************************************************************/
|
* ****************************************************************************/
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||||
double eps = 0.05)
|
double eps = 0.05)
|
||||||
{
|
{
|
||||||
|
|
@ -289,11 +296,13 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||||
return std::sqrt(p.transpose() * p);
|
return std::sqrt(p.transpose() * p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
PointSet normals(const PointSet& points,
|
PointSet normals(const PointSet& points,
|
||||||
const EigenMesh3D& mesh,
|
const EigenMesh3D& mesh,
|
||||||
double eps,
|
double eps,
|
||||||
std::function<void()> thr, // throw on cancel
|
std::function<void()> thr, // throw on cancel
|
||||||
const std::vector<unsigned>& pt_indices = {})
|
const std::vector<unsigned>& pt_indices)
|
||||||
{
|
{
|
||||||
if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -419,9 +428,17 @@ PointSet normals(const PointSet& points,
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace bgi = boost::geometry::index;
|
namespace bgi = boost::geometry::index;
|
||||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
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,
|
ClusteredPoints cluster(Index3D &sindex,
|
||||||
unsigned max_points,
|
unsigned max_points,
|
||||||
std::function<std::vector<PointIndexEl>(
|
std::function<std::vector<PointIndexEl>(
|
||||||
|
|
@ -433,25 +450,22 @@ ClusteredPoints cluster(Index3D &sindex,
|
||||||
// each other
|
// each other
|
||||||
std::function<void(Elems&, Elems&)> group =
|
std::function<void(Elems&, Elems&)> group =
|
||||||
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||||
{
|
{
|
||||||
for(auto& p : pts) {
|
for(auto& p : pts) {
|
||||||
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||||
auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){
|
|
||||||
return e1.second < e2.second;
|
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||||
};
|
|
||||||
|
|
||||||
std::sort(tmp.begin(), tmp.end(), cmp);
|
|
||||||
|
|
||||||
Elems newpts;
|
Elems newpts;
|
||||||
std::set_difference(tmp.begin(), tmp.end(),
|
std::set_difference(tmp.begin(), tmp.end(),
|
||||||
cluster.begin(), cluster.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 c = max_points && newpts.size() + cluster.size() > max_points?
|
||||||
int(max_points - cluster.size()) : int(newpts.size());
|
int(max_points - cluster.size()) : int(newpts.size());
|
||||||
|
|
||||||
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
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))
|
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||||
group(newpts, cluster);
|
group(newpts, cluster);
|
||||||
|
|
@ -479,7 +493,6 @@ ClusteredPoints cluster(Index3D &sindex,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||||
const PointIndexEl& p,
|
const PointIndexEl& p,
|
||||||
double dist,
|
double dist,
|
||||||
|
|
@ -496,7 +509,8 @@ std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||||
|
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// Clustering a set of points by the given criteria
|
// Clustering a set of points by the given criteria
|
||||||
ClusteredPoints cluster(
|
ClusteredPoints cluster(
|
||||||
|
|
@ -558,5 +572,5 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace sla
|
||||||
}
|
} // namespace Slic3r
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include "SLAPrint.hpp"
|
#include "SLAPrint.hpp"
|
||||||
#include "SLA/SLASupportTree.hpp"
|
#include "SLA/SLASupportTree.hpp"
|
||||||
#include "SLA/SLABasePool.hpp"
|
#include "SLA/SLAPad.hpp"
|
||||||
#include "SLA/SLAAutoSupports.hpp"
|
#include "SLA/SLAAutoSupports.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
|
|
@ -32,17 +32,19 @@
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
|
class SLAPrintObject::SupportData : public sla::SupportableMesh
|
||||||
|
|
||||||
class SLAPrintObject::SupportData
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
sla::EigenMesh3D emesh; // index-triangle representation
|
sla::SupportTree::UPtr support_tree_ptr; // the supports
|
||||||
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
|
|
||||||
SupportTreePtr support_tree_ptr; // the supports
|
|
||||||
std::vector<ExPolygons> support_slices; // sliced supports
|
std::vector<ExPolygons> 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 {
|
namespace {
|
||||||
|
|
@ -53,7 +55,7 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS =
|
||||||
30, // slaposObjectSlice,
|
30, // slaposObjectSlice,
|
||||||
20, // slaposSupportPoints,
|
20, // slaposSupportPoints,
|
||||||
10, // slaposSupportTree,
|
10, // slaposSupportTree,
|
||||||
10, // slaposBasePool,
|
10, // slaposPad,
|
||||||
30, // slaposSliceSupports,
|
30, // slaposSliceSupports,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -64,7 +66,7 @@ std::string OBJ_STEP_LABELS(size_t idx)
|
||||||
case slaposObjectSlice: return L("Slicing model");
|
case slaposObjectSlice: return L("Slicing model");
|
||||||
case slaposSupportPoints: return L("Generating support points");
|
case slaposSupportPoints: return L("Generating support points");
|
||||||
case slaposSupportTree: return L("Generating support tree");
|
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");
|
case slaposSliceSupports: return L("Slicing supports");
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
|
|
@ -583,7 +585,8 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) {
|
||||||
// Compile the argument for support creation from the static print config.
|
// Compile the argument for support creation from the static print config.
|
||||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
||||||
sla::SupportConfig scfg;
|
sla::SupportConfig scfg;
|
||||||
|
|
||||||
|
scfg.enabled = c.supports_enable.getBool();
|
||||||
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
|
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_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
|
||||||
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
|
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
|
||||||
|
|
@ -612,12 +615,13 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
||||||
return scfg;
|
return scfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
|
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
|
||||||
sla::PoolConfig::EmbedObject ret;
|
sla::PadConfig::EmbedObject ret;
|
||||||
|
|
||||||
ret.enabled = is_zero_elevation(c);
|
ret.enabled = is_zero_elevation(c);
|
||||||
|
|
||||||
if(ret.enabled) {
|
if(ret.enabled) {
|
||||||
|
ret.everywhere = c.pad_around_object_everywhere.getBool();
|
||||||
ret.object_gap_mm = c.pad_object_gap.getFloat();
|
ret.object_gap_mm = c.pad_object_gap.getFloat();
|
||||||
ret.stick_width_mm = c.pad_object_connector_width.getFloat();
|
ret.stick_width_mm = c.pad_object_connector_width.getFloat();
|
||||||
ret.stick_stride_mm = c.pad_object_connector_stride.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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) {
|
||||||
sla::PoolConfig pcfg;
|
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;
|
pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0;
|
||||||
|
|
||||||
// We do not support radius for now
|
pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat();
|
||||||
pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
|
pcfg.wall_height_mm = c.pad_wall_height.getFloat();
|
||||||
|
pcfg.brim_size_mm = c.pad_brim_size.getFloat();
|
||||||
pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat();
|
|
||||||
pcfg.min_wall_height_mm = c.pad_wall_height.getFloat();
|
|
||||||
|
|
||||||
// set builtin pad implicitly ON
|
// set builtin pad implicitly ON
|
||||||
pcfg.embed_object = builtin_pad_cfg(c);
|
pcfg.embed_object = builtin_pad_cfg(c);
|
||||||
|
|
@ -646,6 +648,13 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
|
||||||
return pcfg;
|
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
|
std::string SLAPrint::validate() const
|
||||||
|
|
@ -663,17 +672,12 @@ std::string SLAPrint::validate() const
|
||||||
|
|
||||||
sla::SupportConfig cfg = make_support_cfg(po->config());
|
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;
|
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(
|
return L(
|
||||||
"Elevation is too low for object. Use the \"Pad around "
|
"Elevation is too low for object. Use the \"Pad around "
|
||||||
"object\" feature to print the object without elevation.");
|
"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' "
|
"distance' has to be greater than the 'Pad object gap' "
|
||||||
"parameter to avoid this.");
|
"parameter to avoid this.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string pval = padcfg.validate();
|
||||||
|
if (!pval.empty()) return pval;
|
||||||
}
|
}
|
||||||
|
|
||||||
double expt_max = m_printer_config.max_exposure_time.getFloat();
|
double expt_max = m_printer_config.max_exposure_time.getFloat();
|
||||||
|
|
@ -876,8 +883,7 @@ void SLAPrint::process()
|
||||||
|
|
||||||
// Construction of this object does the calculation.
|
// Construction of this object does the calculation.
|
||||||
this->throw_if_canceled();
|
this->throw_if_canceled();
|
||||||
SLAAutoSupports auto_supports(po.transformed_mesh(),
|
SLAAutoSupports auto_supports(po.m_supportdata->emesh,
|
||||||
po.m_supportdata->emesh,
|
|
||||||
po.get_model_slices(),
|
po.get_model_slices(),
|
||||||
heights,
|
heights,
|
||||||
config,
|
config,
|
||||||
|
|
@ -887,10 +893,10 @@ void SLAPrint::process()
|
||||||
// Now let's extract the result.
|
// Now let's extract the result.
|
||||||
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||||
this->throw_if_canceled();
|
this->throw_if_canceled();
|
||||||
po.m_supportdata->support_points = points;
|
po.m_supportdata->pts = points;
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Automatic support 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
|
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||||
// the update status to GLGizmoSlaSupports
|
// the update status to GLGizmoSlaSupports
|
||||||
|
|
@ -902,29 +908,19 @@ void SLAPrint::process()
|
||||||
else {
|
else {
|
||||||
// There are either some points on the front-end, or the user
|
// There are either some points on the front-end, or the user
|
||||||
// removed them on purpose. No calculation will be done.
|
// 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
|
// If the zero elevation mode is engaged, we have to filter out all the
|
||||||
// points that are on the bottom of the object
|
// points that are on the bottom of the object
|
||||||
if (is_zero_elevation(po.config())) {
|
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()
|
double tolerance = po.config().pad_enable.getBool()
|
||||||
? po.m_config.pad_wall_thickness.getFloat()
|
? po.m_config.pad_wall_thickness.getFloat()
|
||||||
: po.m_config.support_base_height.getFloat();
|
: po.m_config.support_base_height.getFloat();
|
||||||
|
|
||||||
// get iterator to the reorganized vector end
|
remove_bottom_points(po.m_supportdata->pts,
|
||||||
auto endit = std::remove_if(
|
po.m_supportdata->emesh.ground_level(),
|
||||||
pts.begin(),
|
tolerance);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -933,45 +929,31 @@ void SLAPrint::process()
|
||||||
{
|
{
|
||||||
if(!po.m_supportdata) return;
|
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)
|
if (pcfg.embed_object)
|
||||||
po.m_supportdata->emesh.ground_level_offset(
|
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||||
pcfg.min_wall_thickness_mm);
|
|
||||||
|
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||||
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;
|
|
||||||
|
|
||||||
// scaling for the sub operations
|
// scaling for the sub operations
|
||||||
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||||
double init = m_report_status.status();
|
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;
|
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,
|
m_report_status(*this, current,
|
||||||
OBJ_STEP_LABELS(slaposSupportTree),
|
OBJ_STEP_LABELS(slaposSupportTree),
|
||||||
SlicingStatus::DEFAULT,
|
SlicingStatus::DEFAULT, logmsg);
|
||||||
logmsg);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
ctl.stopcondition = [this]() { return canceled(); };
|
||||||
ctl.stopcondition = [this](){ return canceled(); };
|
|
||||||
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||||
|
|
||||||
po.m_supportdata->support_tree_ptr.reset(
|
po.m_supportdata->create_support_tree(ctl);
|
||||||
new SLASupportTree(po.m_supportdata->support_points,
|
|
||||||
po.m_supportdata->emesh, scfg, ctl));
|
if (!po.m_config.supports_enable.getBool()) return;
|
||||||
|
|
||||||
throw_if_canceled();
|
throw_if_canceled();
|
||||||
|
|
||||||
|
|
@ -980,10 +962,9 @@ void SLAPrint::process()
|
||||||
|
|
||||||
// This is to prevent "Done." being displayed during merged_mesh()
|
// This is to prevent "Done." being displayed during merged_mesh()
|
||||||
m_report_status(*this, -1, L("Visualizing supports"));
|
m_report_status(*this, -1, L("Visualizing supports"));
|
||||||
po.m_supportdata->support_tree_ptr->merged_mesh();
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
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.
|
// Check the mesh for later troubleshooting.
|
||||||
if(po.support_mesh().empty())
|
if(po.support_mesh().empty())
|
||||||
|
|
@ -993,7 +974,7 @@ void SLAPrint::process()
|
||||||
};
|
};
|
||||||
|
|
||||||
// This step generates the sla base pad
|
// 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
|
// 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
|
// and before the supports had been sliced. (or the slicing has to be
|
||||||
// repeated)
|
// repeated)
|
||||||
|
|
@ -1001,10 +982,10 @@ void SLAPrint::process()
|
||||||
if(po.m_config.pad_enable.getBool())
|
if(po.m_config.pad_enable.getBool())
|
||||||
{
|
{
|
||||||
// Get the distilled pad configuration from the config
|
// 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.
|
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();
|
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||||
|
|
||||||
// This call can get pretty time consuming
|
// 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
|
// 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
|
// get a sample from the bottom of the mesh and use it for pad
|
||||||
// creation.
|
// creation.
|
||||||
sla::base_plate(trmesh,
|
sla::pad_blueprint(trmesh, bp, float(pad_h),
|
||||||
bp,
|
float(po.m_config.layer_height.getFloat()),
|
||||||
float(pad_h),
|
thrfn);
|
||||||
float(po.m_config.layer_height.getFloat()),
|
|
||||||
thrfn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pcfg.throw_on_cancel = thrfn;
|
|
||||||
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
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) {
|
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
|
||||||
po.m_supportdata->support_tree_ptr->remove_pad();
|
po.m_supportdata->support_tree_ptr->remove_pad();
|
||||||
}
|
}
|
||||||
|
|
@ -1191,9 +1176,8 @@ void SLAPrint::process()
|
||||||
{
|
{
|
||||||
ClipperPolygon poly;
|
ClipperPolygon poly;
|
||||||
|
|
||||||
// We need to reverse if flpXY OR is_lefthanded is true but
|
// We need to reverse if is_lefthanded is true but
|
||||||
// not if both are true which is a logical inequality (XOR)
|
bool needreverse = is_lefthanded;
|
||||||
bool needreverse = /*flpXY !=*/ is_lefthanded;
|
|
||||||
|
|
||||||
// should be a move
|
// should be a move
|
||||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||||
|
|
@ -1396,7 +1380,7 @@ void SLAPrint::process()
|
||||||
if(canceled()) return;
|
if(canceled()) return;
|
||||||
|
|
||||||
// Set up the printer, allocate space for all the layers
|
// 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());
|
auto lvlcnt = unsigned(m_printer_input.size());
|
||||||
printer.layers(lvlcnt);
|
printer.layers(lvlcnt);
|
||||||
|
|
@ -1416,11 +1400,9 @@ void SLAPrint::process()
|
||||||
|
|
||||||
SpinMutex slck;
|
SpinMutex slck;
|
||||||
|
|
||||||
auto orientation = get_printer_orientation();
|
|
||||||
|
|
||||||
// procedure to process one height level. This will run in parallel
|
// procedure to process one height level. This will run in parallel
|
||||||
auto lvlfn =
|
auto lvlfn =
|
||||||
[this, &slck, &printer, increment, &dstatus, &pst, orientation]
|
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||||
(unsigned level_id)
|
(unsigned level_id)
|
||||||
{
|
{
|
||||||
if(canceled()) return;
|
if(canceled()) return;
|
||||||
|
|
@ -1431,7 +1413,7 @@ void SLAPrint::process()
|
||||||
printer.begin_layer(level_id);
|
printer.begin_layer(level_id);
|
||||||
|
|
||||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
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.
|
// Finish the layer for later saving it.
|
||||||
printer.finish_layer(level_id);
|
printer.finish_layer(level_id);
|
||||||
|
|
@ -1459,7 +1441,7 @@ void SLAPrint::process()
|
||||||
tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
|
tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
|
||||||
|
|
||||||
// Set statistics values to the printer
|
// Set statistics values to the printer
|
||||||
sla::SLARasterWriter::PrintStatistics stats;
|
sla::RasterWriter::PrintStatistics stats;
|
||||||
stats.used_material = (m_print_statistics.objects_used_material +
|
stats.used_material = (m_print_statistics.objects_used_material +
|
||||||
m_print_statistics.support_used_material) /
|
m_print_statistics.support_used_material) /
|
||||||
1000;
|
1000;
|
||||||
|
|
@ -1478,12 +1460,12 @@ void SLAPrint::process()
|
||||||
|
|
||||||
slaposFn pobj_program[] =
|
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...
|
// We want to first process all objects...
|
||||||
std::vector<SLAPrintObjectStep> level1_obj_steps = {
|
std::vector<SLAPrintObjectStep> level1_obj_steps = {
|
||||||
slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool
|
slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
|
||||||
};
|
};
|
||||||
|
|
||||||
// and then slice all supports to allow preview to be displayed ASAP
|
// and then slice all supports to allow preview to be displayed ASAP
|
||||||
|
|
@ -1654,12 +1636,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
||||||
return invalidated;
|
return invalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
sla::SLARasterWriter & SLAPrint::init_printer()
|
sla::RasterWriter & SLAPrint::init_printer()
|
||||||
{
|
{
|
||||||
sla::Raster::Resolution res;
|
sla::Raster::Resolution res;
|
||||||
sla::Raster::PixelDim pxdim;
|
sla::Raster::PixelDim pxdim;
|
||||||
std::array<bool, 2> mirror;
|
std::array<bool, 2> mirror;
|
||||||
double gamma;
|
|
||||||
|
|
||||||
double w = m_printer_config.display_width.getFloat();
|
double w = m_printer_config.display_width.getFloat();
|
||||||
double h = m_printer_config.display_height.getFloat();
|
double h = m_printer_config.display_height.getFloat();
|
||||||
|
|
@ -1669,20 +1650,18 @@ sla::SLARasterWriter & SLAPrint::init_printer()
|
||||||
mirror[X] = m_printer_config.display_mirror_x.getBool();
|
mirror[X] = m_printer_config.display_mirror_x.getBool();
|
||||||
mirror[Y] = m_printer_config.display_mirror_y.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(w, h);
|
||||||
std::swap(pw, ph);
|
std::swap(pw, ph);
|
||||||
|
|
||||||
// XY flipping implicitly does an X mirror
|
|
||||||
mirror[X] = !mirror[X];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = sla::Raster::Resolution{pw, ph};
|
res = sla::Raster::Resolution{pw, ph};
|
||||||
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
|
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
|
||||||
|
sla::Raster::Trafo tr{orientation, mirror};
|
||||||
gamma = m_printer_config.gamma_correction.getFloat();
|
tr.gamma = m_printer_config.gamma_correction.getFloat();
|
||||||
|
|
||||||
m_printer.reset(new sla::SLARasterWriter(res, pxdim, mirror, gamma));
|
m_printer.reset(new sla::RasterWriter(res, pxdim, tr));
|
||||||
m_printer->set_config(m_full_print_config);
|
m_printer->set_config(m_full_print_config);
|
||||||
return *m_printer;
|
return *m_printer;
|
||||||
}
|
}
|
||||||
|
|
@ -1730,6 +1709,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||||
|| opt_key == "supports_enable"
|
|| opt_key == "supports_enable"
|
||||||
|| opt_key == "support_object_elevation"
|
|| opt_key == "support_object_elevation"
|
||||||
|| opt_key == "pad_around_object"
|
|| opt_key == "pad_around_object"
|
||||||
|
|| opt_key == "pad_around_object_everywhere"
|
||||||
|| opt_key == "slice_closing_radius") {
|
|| opt_key == "slice_closing_radius") {
|
||||||
steps.emplace_back(slaposObjectSlice);
|
steps.emplace_back(slaposObjectSlice);
|
||||||
} else if (
|
} else if (
|
||||||
|
|
@ -1754,6 +1734,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||||
steps.emplace_back(slaposSupportTree);
|
steps.emplace_back(slaposSupportTree);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "pad_wall_height"
|
opt_key == "pad_wall_height"
|
||||||
|
|| opt_key == "pad_brim_size"
|
||||||
|| opt_key == "pad_max_merge_distance"
|
|| opt_key == "pad_max_merge_distance"
|
||||||
|| opt_key == "pad_wall_slope"
|
|| opt_key == "pad_wall_slope"
|
||||||
|| opt_key == "pad_edge_radius"
|
|| opt_key == "pad_edge_radius"
|
||||||
|
|
@ -1762,7 +1743,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||||
|| opt_key == "pad_object_connector_width"
|
|| opt_key == "pad_object_connector_width"
|
||||||
|| opt_key == "pad_object_connector_penetration"
|
|| opt_key == "pad_object_connector_penetration"
|
||||||
) {
|
) {
|
||||||
steps.emplace_back(slaposBasePool);
|
steps.emplace_back(slaposPad);
|
||||||
} else {
|
} else {
|
||||||
// All keys should be covered.
|
// All keys should be covered.
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
@ -1782,12 +1763,12 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
|
||||||
if (step == slaposObjectSlice) {
|
if (step == slaposObjectSlice) {
|
||||||
invalidated |= this->invalidate_all_steps();
|
invalidated |= this->invalidate_all_steps();
|
||||||
} else if (step == slaposSupportPoints) {
|
} else if (step == slaposSupportPoints) {
|
||||||
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports });
|
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposPad, slaposSliceSupports });
|
||||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||||
} else if (step == slaposSupportTree) {
|
} else if (step == slaposSupportTree) {
|
||||||
invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports });
|
invalidated |= this->invalidate_steps({ slaposPad, slaposSliceSupports });
|
||||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||||
} else if (step == slaposBasePool) {
|
} else if (step == slaposPad) {
|
||||||
invalidated |= this->invalidate_steps({slaposSliceSupports});
|
invalidated |= this->invalidate_steps({slaposSliceSupports});
|
||||||
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
|
||||||
} else if (step == slaposSliceSupports) {
|
} else if (step == slaposSliceSupports) {
|
||||||
|
|
@ -1813,8 +1794,8 @@ double SLAPrintObject::get_elevation() const {
|
||||||
// its walls but currently it is half of its thickness. Whatever it
|
// 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
|
// will be in the future, we provide the config to the get_pad_elevation
|
||||||
// method and we will have the correct value
|
// method and we will have the correct value
|
||||||
sla::PoolConfig pcfg = make_pool_config(m_config);
|
sla::PadConfig pcfg = make_pad_cfg(m_config);
|
||||||
if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg);
|
if(!pcfg.embed_object) ret += pcfg.required_elevation();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -1825,7 +1806,7 @@ double SLAPrintObject::get_current_elevation() const
|
||||||
if (is_zero_elevation(m_config)) return 0.;
|
if (is_zero_elevation(m_config)) return 0.;
|
||||||
|
|
||||||
bool has_supports = is_step_done(slaposSupportTree);
|
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)
|
if(!has_supports && !has_pad)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1866,7 +1847,7 @@ const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f);
|
||||||
|
|
||||||
const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const
|
const std::vector<sla::SupportPoint>& 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<ExPolygons> &SLAPrintObject::get_support_slices() const
|
const std::vector<ExPolygons> &SLAPrintObject::get_support_slices() const
|
||||||
|
|
@ -1896,7 +1877,7 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case slaposSupportTree:
|
case slaposSupportTree:
|
||||||
return ! this->support_mesh().empty();
|
return ! this->support_mesh().empty();
|
||||||
case slaposBasePool:
|
case slaposPad:
|
||||||
return ! this->pad_mesh().empty();
|
return ! this->pad_mesh().empty();
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1908,7 +1889,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case slaposSupportTree:
|
case slaposSupportTree:
|
||||||
return this->support_mesh();
|
return this->support_mesh();
|
||||||
case slaposBasePool:
|
case slaposPad:
|
||||||
return this->pad_mesh();
|
return this->pad_mesh();
|
||||||
default:
|
default:
|
||||||
return TriangleMesh();
|
return TriangleMesh();
|
||||||
|
|
@ -1917,18 +1898,20 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
|
||||||
|
|
||||||
const TriangleMesh& SLAPrintObject::support_mesh() const
|
const TriangleMesh& SLAPrintObject::support_mesh() const
|
||||||
{
|
{
|
||||||
if(m_config.supports_enable.getBool() && m_supportdata &&
|
sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr;
|
||||||
m_supportdata->support_tree_ptr) {
|
|
||||||
return m_supportdata->support_tree_ptr->merged_mesh();
|
if(m_config.supports_enable.getBool() && m_supportdata && stree)
|
||||||
}
|
return stree->retrieve_mesh(sla::MeshType::Support);
|
||||||
|
|
||||||
return EMPTY_MESH;
|
return EMPTY_MESH;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TriangleMesh& SLAPrintObject::pad_mesh() const
|
const TriangleMesh& SLAPrintObject::pad_mesh() const
|
||||||
{
|
{
|
||||||
if(m_config.pad_enable.getBool() && m_supportdata && m_supportdata->support_tree_ptr)
|
sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr;
|
||||||
return m_supportdata->support_tree_ptr->get_pad();
|
|
||||||
|
if(m_config.pad_enable.getBool() && m_supportdata && stree)
|
||||||
|
return stree->retrieve_mesh(sla::MeshType::Pad);
|
||||||
|
|
||||||
return EMPTY_MESH;
|
return EMPTY_MESH;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ enum SLAPrintObjectStep : unsigned int {
|
||||||
slaposObjectSlice,
|
slaposObjectSlice,
|
||||||
slaposSupportPoints,
|
slaposSupportPoints,
|
||||||
slaposSupportTree,
|
slaposSupportTree,
|
||||||
slaposBasePool,
|
slaposPad,
|
||||||
slaposSliceSupports,
|
slaposSliceSupports,
|
||||||
slaposCount
|
slaposCount
|
||||||
};
|
};
|
||||||
|
|
@ -54,7 +54,7 @@ public:
|
||||||
bool is_left_handed() const { return m_left_handed; }
|
bool is_left_handed() const { return m_left_handed; }
|
||||||
|
|
||||||
struct Instance {
|
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; }
|
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.
|
// ID of the corresponding ModelInstance.
|
||||||
ObjectID instance_id;
|
ObjectID instance_id;
|
||||||
|
|
@ -440,7 +440,7 @@ private:
|
||||||
std::vector<PrintLayer> m_printer_input;
|
std::vector<PrintLayer> m_printer_input;
|
||||||
|
|
||||||
// The printer itself
|
// The printer itself
|
||||||
std::unique_ptr<sla::SLARasterWriter> m_printer;
|
std::unique_ptr<sla::RasterWriter> m_printer;
|
||||||
|
|
||||||
// Estimated print time, material consumed.
|
// Estimated print time, material consumed.
|
||||||
SLAPrintStatistics m_print_statistics;
|
SLAPrintStatistics m_print_statistics;
|
||||||
|
|
@ -459,14 +459,13 @@ private:
|
||||||
double status() const { return m_st; }
|
double status() const { return m_st; }
|
||||||
} m_report_status;
|
} 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();
|
auto ro = m_printer_config.display_orientation.getInt();
|
||||||
return ro == sla::SLARasterWriter::roPortrait ?
|
return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait :
|
||||||
sla::SLARasterWriter::roPortrait :
|
sla::Raster::roLandscape;
|
||||||
sla::SLARasterWriter::roLandscape;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
friend SLAPrintObject;
|
friend SLAPrintObject;
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ namespace Slic3r {
|
||||||
class ExPolygon;
|
class ExPolygon;
|
||||||
typedef std::vector<ExPolygon> ExPolygons;
|
typedef std::vector<ExPolygon> ExPolygons;
|
||||||
|
|
||||||
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false);
|
const bool constexpr NORMALS_UP = false;
|
||||||
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
|
const bool constexpr NORMALS_DOWN = true;
|
||||||
extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false);
|
|
||||||
extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false);
|
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = NORMALS_UP);
|
||||||
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false);
|
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = NORMALS_UP);
|
||||||
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false);
|
extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon &poly, bool flip = NORMALS_UP);
|
||||||
|
extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = NORMALS_UP);
|
||||||
|
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = NORMALS_UP);
|
||||||
|
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP);
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,116 +3,232 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cassert>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
//#include <boost/date_time/local_time/local_time.hpp>
|
#ifdef _MSC_VER
|
||||||
//#include <boost/chrono.hpp>
|
#include <map>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "libslic3r/Utils.hpp"
|
||||||
#ifdef WIN32
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
|
||||||
#undef WIN32_LEAN_AND_MEAN
|
|
||||||
#endif /* WIN32 */
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace Utils {
|
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
|
// ISO8601Z representation of time, without time zone info
|
||||||
#if defined(__GNUC__) && __GNUC__ <= 4
|
static const constexpr char *const ISO8601Z_TIME_FMT = "%Y%m%dT%H%M%SZ";
|
||||||
std::string put_time(const std::tm *tm, const char *fmt)
|
|
||||||
|
static const char * get_fmtstr(TimeFormat fmt)
|
||||||
{
|
{
|
||||||
static const constexpr int MAX_CHARS = 200;
|
switch (fmt) {
|
||||||
char out[MAX_CHARS];
|
case TimeFormat::gcode: return SLICER_UTC_TIME_FMT;
|
||||||
std::strftime(out, MAX_CHARS, fmt, tm);
|
case TimeFormat::iso8601Z: return ISO8601Z_TIME_FMT;
|
||||||
return out;
|
}
|
||||||
|
|
||||||
|
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<std::string, std::string> 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
|
#endif
|
||||||
|
|
||||||
|
template<class Ttm>
|
||||||
|
struct GetPutTimeReturnT {
|
||||||
|
Ttm *tms;
|
||||||
|
const char *fmt;
|
||||||
|
GetPutTimeReturnT(Ttm *_tms, const char *_fmt): tms(_tms), fmt(_fmt) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
using GetTimeReturnT = GetPutTimeReturnT<std::tm>;
|
||||||
|
using PutTimeReturnT = GetPutTimeReturnT<const std::tm>;
|
||||||
|
|
||||||
|
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;
|
return {tms, fmt};
|
||||||
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;
|
std::istream &operator>>(std::istream &stream, GetTimeReturnT &>)
|
||||||
tms.tm_year = y - 1900; // Year since 1900
|
{
|
||||||
tms.tm_mon = M - 1; // 0-11
|
std::string line;
|
||||||
tms.tm_mday = d; // 1-31
|
std::getline(stream, line);
|
||||||
tms.tm_hour = h; // 0-23
|
|
||||||
tms.tm_min = m; // 0-59
|
if (strptime(line.c_str(), gt.fmt, gt.tms) == nullptr)
|
||||||
tms.tm_sec = s; // 0-61 (0-60 in C++11)
|
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
|
#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 */
|
#else /* WIN32 */
|
||||||
return timegm(&tms);
|
return timegm(&_tms);
|
||||||
#endif /* WIN32 */
|
#endif /* WIN32 */
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string format_time_ISO8601Z(time_t time)
|
std::string process_format(const char *fmt, TimeZone zone)
|
||||||
{
|
{
|
||||||
struct tm tms;
|
std::string fmtstr(fmt);
|
||||||
#ifdef WIN32
|
|
||||||
gmtime_s(&tms, &time);
|
if (fmtstr == SLICER_UTC_TIME_FMT && zone == TimeZone::utc)
|
||||||
#else
|
fmtstr += " UTC";
|
||||||
gmtime_r(&time, &tms);
|
|
||||||
#endif
|
return fmtstr;
|
||||||
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 format_local_date_time(time_t time)
|
} // namespace
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
time_t get_current_time_utc()
|
time_t get_current_time_utc()
|
||||||
{
|
{
|
||||||
using clk = std::chrono::system_clock;
|
using clk = std::chrono::system_clock;
|
||||||
return clk::to_time_t(clk::now());
|
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;
|
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();
|
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::string ret;
|
||||||
|
std::tm tms = {};
|
||||||
|
tms.tm_isdst = -1;
|
||||||
|
std::string fmtstr = process_format(get_fmtstr(fmt), zone);
|
||||||
|
|
||||||
switch (zone) {
|
switch (zone) {
|
||||||
case TimeZone::local: ret = tm2str(std::localtime(&t), fmt); break;
|
case TimeZone::local:
|
||||||
case TimeZone::utc: ret = tm2str(std::gmtime(&t), fmt) + " UTC"; break;
|
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;
|
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 Utils
|
||||||
}; // namespace Slic3r
|
}; // namespace Slic3r
|
||||||
|
|
|
||||||
|
|
@ -7,41 +7,61 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|
||||||
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
|
// Should be thread safe.
|
||||||
// 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.
|
|
||||||
time_t get_current_time_utc();
|
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 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);
|
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
|
// String to time_t function. Returns time_t(-1) if fails to parse the input.
|
||||||
}; // namespace Slic3r
|
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_ */
|
#endif /* slic3r_Utils_Time_hpp_ */
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ bool TriangleMesh::needed_repair() const
|
||||||
|| this->stl.stats.backwards_edges > 0;
|
|| 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);
|
its_write_obj(this->its, output_file);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public:
|
||||||
float volume();
|
float volume();
|
||||||
void check_topology();
|
void check_topology();
|
||||||
bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; }
|
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(float factor);
|
||||||
void scale(const Vec3d &versor);
|
void scale(const Vec3d &versor);
|
||||||
void translate(float x, float y, float z);
|
void translate(float x, float y, float z);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,7 @@ std::string string_printf(const char *format, ...)
|
||||||
|
|
||||||
std::string header_slic3r_generated()
|
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()
|
unsigned get_current_pid()
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ void Snapshot::load_ini(const std::string &path)
|
||||||
if (kvp.first == "id")
|
if (kvp.first == "id")
|
||||||
this->id = kvp.second.data();
|
this->id = kvp.second.data();
|
||||||
else if (kvp.first == "time_captured") {
|
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)
|
if (this->time_captured == (time_t)-1)
|
||||||
throw_on_parse_error("invalid timestamp");
|
throw_on_parse_error("invalid timestamp");
|
||||||
} else if (kvp.first == "slic3r_version_captured") {
|
} else if (kvp.first == "slic3r_version_captured") {
|
||||||
|
|
@ -165,7 +165,7 @@ void Snapshot::save_ini(const std::string &path)
|
||||||
// Export the common "snapshot".
|
// Export the common "snapshot".
|
||||||
c << std::endl << "[snapshot]" << std::endl;
|
c << std::endl << "[snapshot]" << std::endl;
|
||||||
c << "id = " << this->id << 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 << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
|
||||||
c << "comment = " << this->comment << std::endl;
|
c << "comment = " << this->comment << std::endl;
|
||||||
c << "reason = " << reason_string(this->reason) << 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 snapshot;
|
||||||
// Snapshot header.
|
// Snapshot header.
|
||||||
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
|
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.slic3r_version_captured = Slic3r::SEMVER;
|
||||||
snapshot.comment = comment;
|
snapshot.comment = comment;
|
||||||
snapshot.reason = reason;
|
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.
|
// Read the active config bundle, parse the config version.
|
||||||
PresetBundle bundle;
|
PresetBundle bundle;
|
||||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
|
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
|
||||||
for (const VendorProfile &vp : bundle.vendors)
|
for (const auto &vp : bundle.vendors)
|
||||||
if (vp.id == cfg.name)
|
if (vp.second.id == cfg.name)
|
||||||
cfg.version.config_version = vp.config_version;
|
cfg.version.config_version = vp.second.config_version;
|
||||||
// Fill-in the min/max slic3r version from the config index, if possible.
|
// Fill-in the min/max slic3r version from the config index, if possible.
|
||||||
try {
|
try {
|
||||||
// Load the config index for the vendor.
|
// Load the config index for the vendor.
|
||||||
|
|
|
||||||
|
|
@ -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_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<int> GLVolumeCollection::load_object(
|
std::vector<int> GLVolumeCollection::load_object(
|
||||||
const ModelObject *model_object,
|
const ModelObject *model_object,
|
||||||
|
|
@ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary(
|
||||||
TriangleMesh convex_hull = mesh.convex_hull_3d();
|
TriangleMesh convex_hull = mesh.convex_hull_3d();
|
||||||
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
||||||
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
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();
|
GLVolume& v = *this->volumes.back();
|
||||||
v.indexed_vertex_array.load_mesh(mesh);
|
v.indexed_vertex_array.load_mesh(mesh);
|
||||||
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
|
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ static const std::string VENDOR_PREFIX = "vendor:";
|
||||||
static const std::string MODEL_PREFIX = "model:";
|
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";
|
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()
|
void AppConfig::reset()
|
||||||
{
|
{
|
||||||
m_storage.clear();
|
m_storage.clear();
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,12 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_section(const std::string §ion) const
|
||||||
|
{ return m_storage.find(section) != m_storage.end(); }
|
||||||
|
const std::map<std::string, std::string>& get_section(const std::string §ion) const
|
||||||
|
{ return m_storage.find(section)->second; }
|
||||||
|
void set_section(const std::string §ion, const std::map<std::string, std::string>& data)
|
||||||
|
{ m_storage[section] = data; }
|
||||||
void clear_section(const std::string §ion)
|
void clear_section(const std::string §ion)
|
||||||
{ m_storage[section].clear(); }
|
{ m_storage[section].clear(); }
|
||||||
|
|
||||||
|
|
@ -125,6 +131,8 @@ public:
|
||||||
std::vector<std::string> get_recent_projects() const;
|
std::vector<std::string> get_recent_projects() const;
|
||||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||||
|
|
||||||
|
static const std::string SECTION_FILAMENTS;
|
||||||
|
static const std::string SECTION_MATERIALS;
|
||||||
private:
|
private:
|
||||||
// Map of section, name -> value
|
// Map of section, name -> value
|
||||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||||
|
|
|
||||||
|
|
@ -349,15 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||||
|
|
||||||
toggle_field("pad_wall_thickness", pad_en);
|
toggle_field("pad_wall_thickness", pad_en);
|
||||||
toggle_field("pad_wall_height", 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_max_merge_distance", pad_en);
|
||||||
// toggle_field("pad_edge_radius", supports_en);
|
// toggle_field("pad_edge_radius", supports_en);
|
||||||
toggle_field("pad_wall_slope", pad_en);
|
toggle_field("pad_wall_slope", pad_en);
|
||||||
toggle_field("pad_around_object", pad_en);
|
toggle_field("pad_around_object", pad_en);
|
||||||
|
toggle_field("pad_around_object_everywhere", pad_en);
|
||||||
|
|
||||||
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
|
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
|
||||||
|
|
||||||
toggle_field("support_object_elevation", supports_en && !zero_elev);
|
toggle_field("support_object_elevation", supports_en && !zero_elev);
|
||||||
toggle_field("pad_object_gap", 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_stride", zero_elev);
|
||||||
toggle_field("pad_object_connector_width", zero_elev);
|
toggle_field("pad_object_connector_width", zero_elev);
|
||||||
toggle_field("pad_object_connector_penetration", zero_elev);
|
toggle_field("pad_object_connector_penetration", zero_elev);
|
||||||
|
|
|
||||||
|
|
@ -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 += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
|
||||||
text += "\">";
|
text += "\">";
|
||||||
text += "<td>";
|
text += "<td>";
|
||||||
|
|
||||||
|
static const constexpr char *LOCALE_TIME_FMT = "%x %X";
|
||||||
|
wxString datetime = wxDateTime(snapshot.time_captured).Format(LOCALE_TIME_FMT);
|
||||||
|
|
||||||
// Format the row header.
|
// Format the row header.
|
||||||
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active")) + ": " : "") +
|
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active")) + ": " : "") +
|
||||||
Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
|
datetime + ": " + format_reason(snapshot.reason);
|
||||||
|
|
||||||
if (! snapshot.comment.empty())
|
if (! snapshot.comment.empty())
|
||||||
text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
|
text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
|
||||||
text += "</b></font><br>";
|
text += "</b></font><br>";
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,15 @@ public:
|
||||||
RR_USER, // User requested the Wizard from the menus
|
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(ConfigWizard &&) = delete;
|
||||||
ConfigWizard(const ConfigWizard &) = delete;
|
ConfigWizard(const ConfigWizard &) = delete;
|
||||||
ConfigWizard &operator=(ConfigWizard &&) = delete;
|
ConfigWizard &operator=(ConfigWizard &&) = delete;
|
||||||
|
|
@ -34,7 +42,7 @@ public:
|
||||||
~ConfigWizard();
|
~ConfigWizard();
|
||||||
|
|
||||||
// Run the Wizard. Return whether it was completed.
|
// 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);
|
static const wxString& name(const bool from_menu = false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,14 @@
|
||||||
#include <wx/choice.h>
|
#include <wx/choice.h>
|
||||||
#include <wx/spinctrl.h>
|
#include <wx/spinctrl.h>
|
||||||
#include <wx/textctrl.h>
|
#include <wx/textctrl.h>
|
||||||
|
#include <wx/listbox.h>
|
||||||
|
#include <wx/checklst.h>
|
||||||
|
#include <wx/radiobut.h>
|
||||||
|
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||||
#include "AppConfig.hpp"
|
#include "AppConfig.hpp"
|
||||||
#include "Preset.hpp"
|
#include "PresetBundle.hpp"
|
||||||
#include "BedShapeDialog.hpp"
|
#include "BedShapeDialog.hpp"
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
|
|
@ -41,6 +44,76 @@ enum {
|
||||||
ROW_SPACING = 75,
|
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<const Preset*> presets;
|
||||||
|
std::set<std::string> 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<class F> 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<PresetBundle> 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<std::string /* = vendor ID */, Bundle>
|
||||||
|
{
|
||||||
|
static BundleMap load();
|
||||||
|
|
||||||
|
Bundle& prusa_bundle();
|
||||||
|
const Bundle& prusa_bundle() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PrinterPickerEvent;
|
||||||
|
|
||||||
|
|
||||||
|
// GUI elements
|
||||||
|
|
||||||
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
|
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
|
||||||
|
|
||||||
struct PrinterPicker: wxPanel
|
struct PrinterPicker: wxPanel
|
||||||
|
|
@ -61,19 +134,22 @@ struct PrinterPicker: wxPanel
|
||||||
std::vector<Checkbox*> cboxes;
|
std::vector<Checkbox*> cboxes;
|
||||||
std::vector<Checkbox*> cboxes_alt;
|
std::vector<Checkbox*> 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, 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);
|
||||||
|
|
||||||
void select_all(bool select, bool alternates = false);
|
void select_all(bool select, bool alternates = false);
|
||||||
void select_one(size_t i, bool select);
|
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; }
|
int get_width() const { return width; }
|
||||||
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
|
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
|
||||||
|
|
||||||
|
static const std::string PRINTER_PLACEHOLDER;
|
||||||
private:
|
private:
|
||||||
int width;
|
int width;
|
||||||
|
|
||||||
std::vector<int> m_button_indexes;
|
std::vector<int> m_button_indexes;
|
||||||
|
|
||||||
|
void on_checkbox(const Checkbox *cbox, bool checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ConfigWizardPage: wxPanel
|
struct ConfigWizardPage: wxPanel
|
||||||
|
|
@ -87,43 +163,107 @@ struct ConfigWizardPage: wxPanel
|
||||||
virtual ~ConfigWizardPage();
|
virtual ~ConfigWizardPage();
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
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);
|
content->Add(thing, proportion, flag, border);
|
||||||
|
return thing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void append_text(wxString text);
|
wxStaticText* append_text(wxString text);
|
||||||
void append_spacer(int space);
|
void append_spacer(int space);
|
||||||
|
|
||||||
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
|
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
|
||||||
|
|
||||||
virtual void apply_custom_config(DynamicPrintConfig &config) {}
|
virtual void apply_custom_config(DynamicPrintConfig &config) {}
|
||||||
|
virtual void set_run_reason(ConfigWizard::RunReason run_reason) {}
|
||||||
|
virtual void on_activate() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PageWelcome: ConfigWizardPage
|
struct PageWelcome: ConfigWizardPage
|
||||||
{
|
{
|
||||||
|
wxStaticText *welcome_text;
|
||||||
wxCheckBox *cbox_reset;
|
wxCheckBox *cbox_reset;
|
||||||
|
|
||||||
PageWelcome(ConfigWizard *parent);
|
PageWelcome(ConfigWizard *parent);
|
||||||
|
|
||||||
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
|
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
|
struct PagePrinters: ConfigWizardPage
|
||||||
{
|
{
|
||||||
enum Technology {
|
|
||||||
// Bitflag equivalent of PrinterTechnology
|
|
||||||
T_FFF = 0x1,
|
|
||||||
T_SLA = 0x2,
|
|
||||||
T_Any = ~0,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<PrinterPicker *> printer_pickers;
|
std::vector<PrinterPicker *> 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);
|
void select_all(bool select, bool alternates = false);
|
||||||
int get_width() const;
|
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<class T, class D> 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<void*>(const_cast<D*>(data));
|
||||||
|
return this->Append(from_u8(label), ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int append(const wxString &label, const D *data) {
|
||||||
|
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
|
||||||
|
return this->Append(label, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const D& get_data(int n) {
|
||||||
|
return *reinterpret_cast<const D*>(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<wxListBox, std::string> StringList;
|
||||||
|
typedef DataList<wxCheckListBox, Preset> 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
|
struct PageCustom: ConfigWizardPage
|
||||||
|
|
@ -150,13 +290,22 @@ struct PageUpdate: ConfigWizardPage
|
||||||
PageUpdate(ConfigWizard *parent);
|
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
|
struct PageVendors: ConfigWizardPage
|
||||||
{
|
{
|
||||||
std::vector<PrinterPicker*> pickers;
|
|
||||||
|
|
||||||
PageVendors(ConfigWizard *parent);
|
PageVendors(ConfigWizard *parent);
|
||||||
|
|
||||||
void on_vendor_pick(size_t i);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PageFirmware: ConfigWizardPage
|
struct PageFirmware: ConfigWizardPage
|
||||||
|
|
@ -194,6 +343,8 @@ struct PageTemperatures: ConfigWizardPage
|
||||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef std::map<std::string /* = vendor ID */, PagePrinters*> Pages3rdparty;
|
||||||
|
|
||||||
|
|
||||||
class ConfigWizardIndex: public wxPanel
|
class ConfigWizardIndex: public wxPanel
|
||||||
{
|
{
|
||||||
|
|
@ -210,12 +361,14 @@ public:
|
||||||
void go_prev();
|
void go_prev();
|
||||||
void go_next();
|
void go_next();
|
||||||
void go_to(size_t i);
|
void go_to(size_t i);
|
||||||
void go_to(ConfigWizardPage *page);
|
void go_to(const ConfigWizardPage *page);
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
void msw_rescale();
|
void msw_rescale();
|
||||||
|
|
||||||
int em() const { return em_w; }
|
int em() const { return em_w; }
|
||||||
|
|
||||||
|
static const size_t NO_ITEM = size_t(-1);
|
||||||
private:
|
private:
|
||||||
struct Item
|
struct Item
|
||||||
{
|
{
|
||||||
|
|
@ -228,12 +381,6 @@ private:
|
||||||
|
|
||||||
int em_w;
|
int em_w;
|
||||||
int em_h;
|
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 bg;
|
||||||
ScalableBitmap bullet_black;
|
ScalableBitmap bullet_black;
|
||||||
ScalableBitmap bullet_blue;
|
ScalableBitmap bullet_blue;
|
||||||
|
|
@ -245,9 +392,6 @@ private:
|
||||||
ssize_t item_hover;
|
ssize_t item_hover;
|
||||||
size_t last_page;
|
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; }
|
int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; }
|
||||||
|
|
||||||
void on_paint(wxPaintEvent &evt);
|
void on_paint(wxPaintEvent &evt);
|
||||||
|
|
@ -256,14 +400,24 @@ private:
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ConfigWizard private data
|
||||||
|
|
||||||
struct ConfigWizard::priv
|
struct ConfigWizard::priv
|
||||||
{
|
{
|
||||||
ConfigWizard *q;
|
ConfigWizard *q;
|
||||||
ConfigWizard::RunReason run_reason;
|
ConfigWizard::RunReason run_reason = RR_USER;
|
||||||
AppConfig appconfig_vendors;
|
AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI
|
||||||
std::unordered_map<std::string, VendorProfile> vendors;
|
BundleMap bundles; // Holds all loaded config bundles, the key is the vendor names.
|
||||||
std::unordered_map<std::string, std::string> vendors_rsrc;
|
// Materials refers to Presets in those bundles by pointers.
|
||||||
std::unique_ptr<DynamicPrintConfig> custom_config;
|
// 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<DynamicPrintConfig> 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;
|
wxScrolledWindow *hscroll = nullptr;
|
||||||
wxBoxSizer *hscroll_sizer = nullptr;
|
wxBoxSizer *hscroll_sizer = nullptr;
|
||||||
|
|
@ -279,9 +433,13 @@ struct ConfigWizard::priv
|
||||||
PageWelcome *page_welcome = nullptr;
|
PageWelcome *page_welcome = nullptr;
|
||||||
PagePrinters *page_fff = nullptr;
|
PagePrinters *page_fff = nullptr;
|
||||||
PagePrinters *page_msla = nullptr;
|
PagePrinters *page_msla = nullptr;
|
||||||
|
PageMaterials *page_filaments = nullptr;
|
||||||
|
PageMaterials *page_sla_materials = nullptr;
|
||||||
PageCustom *page_custom = nullptr;
|
PageCustom *page_custom = nullptr;
|
||||||
PageUpdate *page_update = nullptr;
|
PageUpdate *page_update = nullptr;
|
||||||
PageVendors *page_vendors = nullptr; // XXX: ?
|
PageMode *page_mode = nullptr;
|
||||||
|
PageVendors *page_vendors = nullptr;
|
||||||
|
Pages3rdparty pages_3rdparty;
|
||||||
|
|
||||||
// Custom setup pages
|
// Custom setup pages
|
||||||
PageFirmware *page_firmware = nullptr;
|
PageFirmware *page_firmware = nullptr;
|
||||||
|
|
@ -289,17 +447,30 @@ struct ConfigWizard::priv
|
||||||
PageDiameters *page_diams = nullptr;
|
PageDiameters *page_diams = nullptr;
|
||||||
PageTemperatures *page_temps = nullptr;
|
PageTemperatures *page_temps = nullptr;
|
||||||
|
|
||||||
priv(ConfigWizard *q) : q(q) {}
|
// Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex)
|
||||||
|
std::vector<ConfigWizardPage*> 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();
|
void init_dialog_size();
|
||||||
|
|
||||||
bool check_first_variant() const;
|
|
||||||
void load_vendors();
|
void load_vendors();
|
||||||
void add_page(ConfigWizardPage *page);
|
void add_page(ConfigWizardPage *page);
|
||||||
void enable_next(bool enable);
|
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);
|
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1747,101 +1747,114 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
_set_current();
|
_set_current();
|
||||||
|
|
||||||
struct ModelVolumeState {
|
struct ModelVolumeState {
|
||||||
ModelVolumeState(const GLVolume *volume) :
|
ModelVolumeState(const GLVolume* volume) :
|
||||||
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
|
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) :
|
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) {}
|
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) :
|
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) {}
|
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); }
|
bool new_geometry() const { return this->volume_idx == size_t(-1); }
|
||||||
const ModelVolume *model_volume;
|
const ModelVolume* model_volume;
|
||||||
// ObjectID of ModelVolume + ObjectID of ModelInstance
|
// ObjectID of ModelVolume + ObjectID of ModelInstance
|
||||||
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
|
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
|
||||||
std::pair<size_t, size_t> geometry_id;
|
std::pair<size_t, size_t> geometry_id;
|
||||||
GLVolume::CompositeID composite_id;
|
GLVolume::CompositeID composite_id;
|
||||||
// Volume index in the new GLVolume vector.
|
// Volume index in the new GLVolume vector.
|
||||||
size_t volume_idx;
|
size_t volume_idx;
|
||||||
};
|
};
|
||||||
std::vector<ModelVolumeState> model_volume_state;
|
std::vector<ModelVolumeState> model_volume_state;
|
||||||
std::vector<ModelVolumeState> aux_volume_state;
|
std::vector<ModelVolumeState> 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.
|
// SLA steps to pull the preview meshes for.
|
||||||
typedef std::array<SLAPrintObjectStep, 2> SLASteps;
|
typedef std::array<SLAPrintObjectStep, 2> SLASteps;
|
||||||
SLASteps sla_steps = { slaposSupportTree, slaposBasePool };
|
SLASteps sla_steps = { slaposSupportTree, slaposPad };
|
||||||
struct SLASupportState {
|
struct SLASupportState {
|
||||||
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
|
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
|
||||||
};
|
};
|
||||||
// State of the sla_steps for all SLAPrintObjects.
|
// State of the sla_steps for all SLAPrintObjects.
|
||||||
std::vector<SLASupportState> sla_support_state;
|
std::vector<SLASupportState> sla_support_state;
|
||||||
|
|
||||||
std::vector<size_t> instance_ids_selected;
|
std::vector<size_t> instance_ids_selected;
|
||||||
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
|
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
|
||||||
|
std::vector<GLVolumeState> deleted_volumes;
|
||||||
std::vector<GLVolume*> glvolumes_new;
|
std::vector<GLVolume*> glvolumes_new;
|
||||||
glvolumes_new.reserve(m_volumes.volumes.size());
|
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;
|
int volume_idx_wipe_tower_old = -1;
|
||||||
|
|
||||||
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
|
// 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.
|
// First initialize model_volumes_new_sorted & model_instances_new_sorted.
|
||||||
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) {
|
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) {
|
||||||
const ModelObject *model_object = m_model->objects[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) {
|
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) {
|
||||||
const ModelInstance *model_instance = model_object->instances[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) {
|
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) {
|
||||||
const ModelVolume *model_volume = model_object->volumes[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));
|
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (printer_technology == ptSLA) {
|
if (printer_technology == ptSLA) {
|
||||||
const SLAPrint *sla_print = this->sla_print();
|
const SLAPrint* sla_print = this->sla_print();
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// Verify that the SLAPrint object is synchronized with m_model.
|
// Verify that the SLAPrint object is synchronized with m_model.
|
||||||
check_model_ids_equal(*m_model, sla_print->model());
|
check_model_ids_equal(*m_model, sla_print->model());
|
||||||
#endif /* NDEBUG */
|
#endif /* NDEBUG */
|
||||||
sla_support_state.reserve(sla_print->objects().size());
|
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;
|
SLASupportState state;
|
||||||
for (size_t istep = 0; istep < sla_steps.size(); ++ istep) {
|
for (size_t istep = 0; istep < sla_steps.size(); ++istep) {
|
||||||
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
|
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
|
||||||
if (state.step[istep].state == PrintStateBase::DONE) {
|
if (state.step[istep].state == PrintStateBase::DONE) {
|
||||||
if (! print_object->has_mesh(sla_steps[istep]))
|
if (!print_object->has_mesh(sla_steps[istep]))
|
||||||
// Consider the DONE step without a valid mesh as invalid for the purpose
|
// Consider the DONE step without a valid mesh as invalid for the purpose
|
||||||
// of mesh visualization.
|
// of mesh visualization.
|
||||||
state.step[istep].state = PrintStateBase::INVALID;
|
state.step[istep].state = PrintStateBase::INVALID;
|
||||||
else
|
else
|
||||||
for (const ModelInstance *model_instance : print_object->model_object()->instances)
|
for (const ModelInstance* model_instance : print_object->model_object()->instances)
|
||||||
// Only the instances, which are currently printable, will have the SLA support structures kept.
|
// 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.
|
// The instances outside the print bed will have the GLVolumes of their support structures released.
|
||||||
if (model_instance->is_printable())
|
if (model_instance->is_printable())
|
||||||
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
|
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(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.
|
// 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) {
|
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) {
|
||||||
GLVolume *volume = m_volumes.volumes[volume_id];
|
GLVolume* volume = m_volumes.volumes[volume_id];
|
||||||
ModelVolumeState key(volume);
|
ModelVolumeState key(volume);
|
||||||
ModelVolumeState *mvs = nullptr;
|
ModelVolumeState* mvs = nullptr;
|
||||||
if (volume->volume_idx() < 0) {
|
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)
|
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
|
// 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.
|
// 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)
|
if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints)
|
||||||
mvs = &(*it);
|
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)
|
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.
|
// 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().
|
// The wipe tower has its own wipe_tower_instance_id().
|
||||||
|
|
@ -1854,19 +1867,23 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
assert(volume_idx_wipe_tower_old == -1);
|
assert(volume_idx_wipe_tower_old == -1);
|
||||||
volume_idx_wipe_tower_old = (int)volume_id;
|
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;
|
delete volume;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
// This GLVolume will be reused.
|
// This GLVolume will be reused.
|
||||||
volume->set_sla_shift_z(0.0);
|
volume->set_sla_shift_z(0.0);
|
||||||
map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
|
map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
|
||||||
mvs->volume_idx = glvolumes_new.size();
|
mvs->volume_idx = glvolumes_new.size();
|
||||||
glvolumes_new.emplace_back(volume);
|
glvolumes_new.emplace_back(volume);
|
||||||
// Update color of the volume based on the current extruder.
|
// Update color of the volume based on the current extruder.
|
||||||
if (mvs->model_volume != nullptr) {
|
if (mvs->model_volume != nullptr) {
|
||||||
int extruder_id = mvs->model_volume->extruder_id();
|
int extruder_id = mvs->model_volume->extruder_id();
|
||||||
if (extruder_id != -1)
|
if (extruder_id != -1)
|
||||||
volume->extruder_id = extruder_id;
|
volume->extruder_id = extruder_id;
|
||||||
|
|
||||||
volume->is_modifier = !mvs->model_volume->is_model_part();
|
volume->is_modifier = !mvs->model_volume->is_model_part();
|
||||||
volume->set_color_from_model_volume(mvs->model_volume);
|
volume->set_color_from_model_volume(mvs->model_volume);
|
||||||
|
|
@ -1884,6 +1901,16 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
|
|
||||||
bool update_object_list = false;
|
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)
|
if (m_volumes.volumes != glvolumes_new)
|
||||||
update_object_list = true;
|
update_object_list = true;
|
||||||
m_volumes.volumes = std::move(glvolumes_new);
|
m_volumes.volumes = std::move(glvolumes_new);
|
||||||
|
|
@ -1898,9 +1925,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);
|
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
|
||||||
if (it->new_geometry()) {
|
if (it->new_geometry()) {
|
||||||
// New volume.
|
// New volume.
|
||||||
|
unsigned int old_id = find_old_volume_id(it->composite_id);
|
||||||
|
if (old_id != -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.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;
|
m_volumes.volumes.back()->geometry_id = key.geometry_id;
|
||||||
update_object_list = true;
|
update_object_list = true;
|
||||||
} else {
|
} else {
|
||||||
// Recycling an old GLVolume.
|
// Recycling an old GLVolume.
|
||||||
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
|
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
|
||||||
|
|
@ -1999,19 +2029,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
||||||
|
|
||||||
const Print *print = m_process->fff_print();
|
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;
|
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
double layer_height = print_config.opt_float("layer_height");
|
double layer_height = print_config.opt_float("layer_height");
|
||||||
double first_layer_height = print_config.get_abs_value("first_layer_height", 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(
|
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),
|
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)
|
if (volume_idx_wipe_tower_old != -1)
|
||||||
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||||
}
|
}
|
||||||
|
|
@ -2634,19 +2662,6 @@ std::string format_mouse_event_debug_message(const wxMouseEvent &evt)
|
||||||
|
|
||||||
void GLCanvas3D::on_mouse(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
|
#if ENABLE_RETINA_GL
|
||||||
const float scale = m_retina_helper->get_scale_factor();
|
const float scale = m_retina_helper->get_scale_factor();
|
||||||
evt.SetX(evt.GetX() * scale);
|
evt.SetX(evt.GetX() * scale);
|
||||||
|
|
@ -3483,6 +3498,20 @@ void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
|
||||||
m_volumes.export_toolpaths_to_obj(filename);
|
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
|
bool GLCanvas3D::_is_shown_on_screen() const
|
||||||
{
|
{
|
||||||
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
|
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
|
||||||
|
|
@ -5284,20 +5313,18 @@ void GLCanvas3D::_load_fff_shells()
|
||||||
// adds wipe tower's volume
|
// adds wipe tower's volume
|
||||||
double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
||||||
const PrintConfig& config = print->config();
|
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) {
|
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;
|
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
double layer_height = print_config.opt_float("layer_height");
|
double layer_height = print_config.opt_float("layer_height");
|
||||||
double first_layer_height = print_config.get_abs_value("first_layer_height", 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,
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5340,8 +5367,8 @@ void GLCanvas3D::_load_sla_shells()
|
||||||
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
||||||
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
|
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
|
||||||
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
|
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
|
||||||
if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool))
|
if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad))
|
||||||
add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
|
add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
|
||||||
}
|
}
|
||||||
double shift_z = obj->get_current_elevation();
|
double shift_z = obj->get_current_elevation();
|
||||||
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
|
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,8 @@ public:
|
||||||
bool has_toolpaths_to_export() const;
|
bool has_toolpaths_to_export() const;
|
||||||
void export_toolpaths_to_obj(const char* filename) const;
|
void export_toolpaths_to_obj(const char* filename) const;
|
||||||
|
|
||||||
|
void mouse_up_cleanup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _is_shown_on_screen() const;
|
bool _is_shown_on_screen() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,9 @@ void GLCanvas3DManager::GLInfo::detect() const
|
||||||
m_renderer = data;
|
m_renderer = data;
|
||||||
|
|
||||||
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
|
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
|
||||||
|
|
||||||
|
m_max_tex_size /= 2;
|
||||||
|
|
||||||
if (GLEW_EXT_texture_filter_anisotropic)
|
if (GLEW_EXT_texture_filter_anisotropic)
|
||||||
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy));
|
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ void GLTexture::Compressor::compress()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
|
// 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
|
// 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<unsigned char>(level.w * level.h * 2, 0);
|
level.compressed_data = std::vector<unsigned char>(std::max((unsigned int)16, level.w * level.h * 2), 0);
|
||||||
int compressed_size = 0;
|
int compressed_size = 0;
|
||||||
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
|
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
|
||||||
level.compressed_data.resize(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_w = m_width;
|
||||||
int lod_h = m_height;
|
int lod_h = m_height;
|
||||||
GLint level = 0;
|
GLint level = 0;
|
||||||
// we do not need to generate all levels down to 1x1
|
while ((lod_w > 1) || (lod_h > 1))
|
||||||
while ((lod_w > 16) || (lod_h > 16))
|
|
||||||
{
|
{
|
||||||
++level;
|
++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_w = m_width;
|
||||||
int lod_h = m_height;
|
int lod_h = m_height;
|
||||||
GLint level = 0;
|
GLint level = 0;
|
||||||
// we do not need to generate all levels down to 1x1
|
while ((lod_w > 1) || (lod_h > 1))
|
||||||
while ((lod_w > 16) || (lod_h > 16))
|
|
||||||
{
|
{
|
||||||
++level;
|
++level;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,49 +101,6 @@ const std::string& shortkey_alt_prefix()
|
||||||
return str;
|
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<ConfigWizard::RunReason>(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)
|
// 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*/)
|
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,6 @@ extern AppConfig* get_app_config();
|
||||||
|
|
||||||
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
|
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
|
// 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);
|
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
#include "../Utils/PresetUpdater.hpp"
|
#include "../Utils/PresetUpdater.hpp"
|
||||||
#include "../Utils/PrintHost.hpp"
|
#include "../Utils/PrintHost.hpp"
|
||||||
#include "../Utils/MacDarkMode.hpp"
|
#include "../Utils/MacDarkMode.hpp"
|
||||||
#include "ConfigWizard.hpp"
|
|
||||||
#include "slic3r/Config/Snapshot.hpp"
|
#include "slic3r/Config/Snapshot.hpp"
|
||||||
#include "ConfigSnapshotDialog.hpp"
|
#include "ConfigSnapshotDialog.hpp"
|
||||||
#include "FirmwareDialog.hpp"
|
#include "FirmwareDialog.hpp"
|
||||||
|
|
@ -46,6 +45,7 @@
|
||||||
#include "Tab.hpp"
|
#include "Tab.hpp"
|
||||||
#include "SysInfoDialog.hpp"
|
#include "SysInfoDialog.hpp"
|
||||||
#include "KBShortcutsDialog.hpp"
|
#include "KBShortcutsDialog.hpp"
|
||||||
|
#include "UpdateDialogs.hpp"
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
#include <Shlobj.h>
|
#include <Shlobj.h>
|
||||||
|
|
@ -148,6 +148,7 @@ GUI_App::GUI_App()
|
||||||
: wxApp()
|
: wxApp()
|
||||||
, m_em_unit(10)
|
, m_em_unit(10)
|
||||||
, m_imgui(new ImGuiWrapper())
|
, m_imgui(new ImGuiWrapper())
|
||||||
|
, m_wizard(nullptr)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
GUI_App::~GUI_App()
|
GUI_App::~GUI_App()
|
||||||
|
|
@ -204,7 +205,6 @@ bool GUI_App::on_init_inner()
|
||||||
// supplied as argument to --datadir; in that case we should still run the wizard
|
// supplied as argument to --datadir; in that case we should still run the wizard
|
||||||
preset_bundle->setup_directories();
|
preset_bundle->setup_directories();
|
||||||
|
|
||||||
app_conf_exists = app_config->exists();
|
|
||||||
// load settings
|
// load settings
|
||||||
app_conf_exists = app_config->exists();
|
app_conf_exists = app_config->exists();
|
||||||
if (app_conf_exists) {
|
if (app_conf_exists) {
|
||||||
|
|
@ -287,7 +287,7 @@ bool GUI_App::on_init_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
CallAfter([this] {
|
CallAfter([this] {
|
||||||
config_wizard_startup(app_conf_exists);
|
config_wizard_startup();
|
||||||
preset_updater->slic3r_update_notify();
|
preset_updater->slic3r_update_notify();
|
||||||
preset_updater->sync(preset_bundle);
|
preset_updater->sync(preset_bundle);
|
||||||
});
|
});
|
||||||
|
|
@ -826,7 +826,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||||
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
|
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
|
||||||
switch (event.GetId() - config_id_base) {
|
switch (event.GetId() - config_id_base) {
|
||||||
case ConfigMenuWizard:
|
case ConfigMenuWizard:
|
||||||
config_wizard(ConfigWizard::RR_USER);
|
run_wizard(ConfigWizard::RR_USER);
|
||||||
break;
|
break;
|
||||||
case ConfigMenuTakeSnapshot:
|
case ConfigMenuTakeSnapshot:
|
||||||
// Take a configuration snapshot.
|
// Take a configuration snapshot.
|
||||||
|
|
@ -1057,6 +1057,31 @@ void GUI_App::open_web_page_localized(const std::string &http_address)
|
||||||
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe());
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
|
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
|
||||||
{
|
{
|
||||||
if (name.empty()) { return; }
|
if (name.empty()) { return; }
|
||||||
|
|
@ -1105,6 +1130,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
|
// static method accepting a wxWindow object as first parameter
|
||||||
// void warning_catcher{
|
// void warning_catcher{
|
||||||
// my($self, $message_dialog) = @_;
|
// my($self, $message_dialog) = @_;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "MainFrame.hpp"
|
#include "MainFrame.hpp"
|
||||||
#include "ImGuiWrapper.hpp"
|
#include "ImGuiWrapper.hpp"
|
||||||
|
#include "ConfigWizard.hpp"
|
||||||
|
|
||||||
#include <wx/app.h>
|
#include <wx/app.h>
|
||||||
#include <wx/colour.h>
|
#include <wx/colour.h>
|
||||||
|
|
@ -69,6 +70,7 @@ enum ConfigMenuIDs {
|
||||||
};
|
};
|
||||||
|
|
||||||
class Tab;
|
class Tab;
|
||||||
|
class ConfigWizard;
|
||||||
|
|
||||||
static wxString dots("…", wxConvUTF8);
|
static wxString dots("…", wxConvUTF8);
|
||||||
|
|
||||||
|
|
@ -96,6 +98,7 @@ class GUI_App : public wxApp
|
||||||
|
|
||||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||||
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||||
|
ConfigWizard* m_wizard; // Managed by wxWindow tree
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool OnInit() override;
|
bool OnInit() override;
|
||||||
|
|
@ -184,6 +187,7 @@ public:
|
||||||
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
|
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
|
||||||
|
|
||||||
void open_web_page_localized(const std::string &http_address);
|
void open_web_page_localized(const std::string &http_address);
|
||||||
|
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool on_init_inner();
|
bool on_init_inner();
|
||||||
|
|
@ -191,6 +195,9 @@ private:
|
||||||
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
|
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
|
||||||
void window_pos_sanitize(wxTopLevelWindow* window);
|
void window_pos_sanitize(wxTopLevelWindow* window);
|
||||||
bool select_language();
|
bool select_language();
|
||||||
|
|
||||||
|
bool config_wizard_startup();
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
void associate_3mf_files();
|
void associate_3mf_files();
|
||||||
#endif // __WXMSW__
|
#endif // __WXMSW__
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,8 @@ void ObjectList::create_objects_ctrl()
|
||||||
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
|
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
|
||||||
|
|
||||||
// column Extruder of the view control:
|
// 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:
|
// column ItemEditing of the view control:
|
||||||
AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
|
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;
|
(*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)
|
void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < m_objects->size(); ++i)
|
for (size_t i = 0; i < m_objects->size(); ++i)
|
||||||
|
|
@ -462,7 +450,7 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
|
||||||
else
|
else
|
||||||
extruder = wxString::Format("%d", object->config.option<ConfigOptionInt>("extruder")->value);
|
extruder = wxString::Format("%d", object->config.option<ConfigOptionInt>("extruder")->value);
|
||||||
|
|
||||||
m_objects_model->SetValue(extruder, item, colExtruder);
|
m_objects_model->SetExtruder(extruder, item);
|
||||||
|
|
||||||
if (object->volumes.size() > 1) {
|
if (object->volumes.size() > 1) {
|
||||||
for (size_t id = 0; id < object->volumes.size(); id++) {
|
for (size_t id = 0; id < object->volumes.size(); id++) {
|
||||||
|
|
@ -474,7 +462,7 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
|
||||||
else
|
else
|
||||||
extruder = wxString::Format("%d", object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value);
|
extruder = wxString::Format("%d", object->volumes[id]->config.option<ConfigOptionInt>("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)
|
if (printer_technology() == ptSLA)
|
||||||
extruders_count = 1;
|
extruders_count = 1;
|
||||||
|
|
||||||
wxDataViewChoiceRenderer* ch_render = dynamic_cast<wxDataViewChoiceRenderer*>(GetColumn(colExtruder)->GetRenderer());
|
|
||||||
if (ch_render->GetChoices().GetCount() - 1 == extruders_count)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_prevent_update_extruder_in_config = true;
|
m_prevent_update_extruder_in_config = true;
|
||||||
|
|
||||||
if (m_objects && extruders_count > 1)
|
if (m_objects && extruders_count > 1)
|
||||||
update_extruder_values_for_items(extruders_count);
|
update_extruder_values_for_items(extruders_count);
|
||||||
|
|
||||||
// delete old extruder column
|
update_extruder_colors();
|
||||||
DeleteColumn(GetColumn(colExtruder));
|
|
||||||
// insert new created extruder column
|
|
||||||
InsertColumn(colExtruder, create_objects_list_extruder_column(extruders_count));
|
|
||||||
// set show/hide for this column
|
// set show/hide for this column
|
||||||
set_extruder_column_hidden(extruders_count <= 1);
|
set_extruder_column_hidden(extruders_count <= 1);
|
||||||
//a workaround for a wrong last column width updating under OSX
|
//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;
|
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
|
void ObjectList::set_extruder_column_hidden(const bool hide) const
|
||||||
{
|
{
|
||||||
GetColumn(colExtruder)->SetHidden(hide);
|
GetColumn(colExtruder)->SetHidden(hide);
|
||||||
|
|
@ -535,14 +522,10 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
|
||||||
m_config = &get_item_config(item);
|
m_config = &get_item_config(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
wxVariant variant;
|
if (!m_config)
|
||||||
m_objects_model->GetValue(variant, item, colExtruder);
|
|
||||||
const wxString selection = variant.GetString();
|
|
||||||
|
|
||||||
if (!m_config || selection.empty())
|
|
||||||
return;
|
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));
|
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
|
||||||
|
|
||||||
// update scene
|
// update scene
|
||||||
|
|
@ -795,7 +778,13 @@ void ObjectList::OnChar(wxKeyEvent& event)
|
||||||
|
|
||||||
void ObjectList::OnContextMenu(wxDataViewEvent&)
|
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*/)
|
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();
|
const wxPoint pt = get_mouse_position_in_control();
|
||||||
HitTest(pt, item, col);
|
HitTest(pt, item, col);
|
||||||
|
|
||||||
|
if (m_extruder_editor)
|
||||||
|
m_extruder_editor->Hide();
|
||||||
|
|
||||||
/* Note: Under OSX right click doesn't send "selection changed" event.
|
/* Note: Under OSX right click doesn't send "selection changed" event.
|
||||||
* It means that Selection() will be return still previously selected item.
|
* It means that Selection() will be return still previously selected item.
|
||||||
* Thus under OSX we should force UnselectAll(), when item and col are nullptr,
|
* 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();
|
fix_through_netfabb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// workaround for extruder editing under OSX
|
||||||
|
else if (wxOSX && evt_context_menu && title == _("Extruder"))
|
||||||
|
extruder_editing();
|
||||||
|
|
||||||
#ifndef __WXMSW__
|
#ifndef __WXMSW__
|
||||||
GetMainWindow()->SetToolTip(""); // hide tooltip
|
GetMainWindow()->SetToolTip(""); // hide tooltip
|
||||||
|
|
@ -894,6 +889,74 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
|
||||||
wxGetApp().plater()->PopupMenu(menu);
|
wxGetApp().plater()->PopupMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectList::extruder_editing()
|
||||||
|
{
|
||||||
|
wxDataViewItem item = GetSelection();
|
||||||
|
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<wxBitmap*> 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()
|
void ObjectList::copy()
|
||||||
{
|
{
|
||||||
// if (m_selection_mode & smLayer)
|
// if (m_selection_mode & smLayer)
|
||||||
|
|
@ -1514,6 +1577,12 @@ void ObjectList::append_menu_item_export_stl(wxMenu* menu) const
|
||||||
menu->AppendSeparator();
|
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
|
void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const
|
||||||
{
|
{
|
||||||
const wxString name = _(L("Change extruder"));
|
const wxString name = _(L("Change extruder"));
|
||||||
|
|
@ -1563,6 +1632,7 @@ void ObjectList::create_object_popupmenu(wxMenu *menu)
|
||||||
append_menu_items_osx(menu);
|
append_menu_items_osx(menu);
|
||||||
#endif // __WXOSX__
|
#endif // __WXOSX__
|
||||||
|
|
||||||
|
append_menu_item_reload_from_disk(menu);
|
||||||
append_menu_item_export_stl(menu);
|
append_menu_item_export_stl(menu);
|
||||||
append_menu_item_fix_through_netfabb(menu);
|
append_menu_item_fix_through_netfabb(menu);
|
||||||
append_menu_item_scale_selection_to_fit_print_volume(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);
|
append_menu_items_osx(menu);
|
||||||
#endif // __WXOSX__
|
#endif // __WXOSX__
|
||||||
|
|
||||||
|
append_menu_item_reload_from_disk(menu);
|
||||||
append_menu_item_export_stl(menu);
|
append_menu_item_export_stl(menu);
|
||||||
append_menu_item_fix_through_netfabb(menu);
|
append_menu_item_fix_through_netfabb(menu);
|
||||||
// rest of a object_sla_menu will be added later in:
|
// 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);
|
append_menu_items_osx(menu);
|
||||||
#endif // __WXOSX__
|
#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_export_stl(menu);
|
||||||
|
append_menu_item_fix_through_netfabb(menu);
|
||||||
|
|
||||||
append_menu_item_split(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()
|
void ObjectList::part_selection_changed()
|
||||||
{
|
{
|
||||||
|
if (m_extruder_editor) m_extruder_editor->Hide();
|
||||||
int obj_idx = -1;
|
int obj_idx = -1;
|
||||||
int volume_id = -1;
|
int volume_id = -1;
|
||||||
m_config = nullptr;
|
m_config = nullptr;
|
||||||
|
|
@ -2341,7 +2414,8 @@ void ObjectList::part_selection_changed()
|
||||||
wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " ");
|
wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " ");
|
||||||
|
|
||||||
if (item) {
|
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));
|
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<ItemForDelete>& it
|
||||||
(*m_objects)[item->obj_idx]->config.has("extruder"))
|
(*m_objects)[item->obj_idx]->config.has("extruder"))
|
||||||
{
|
{
|
||||||
const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option<ConfigOptionInt>("extruder")->value);
|
const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option<ConfigOptionInt>("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);
|
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.
|
/* We can change extruder for Object/Volume only.
|
||||||
* So, if Instance is selected, get its Object item and change it
|
* 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) :
|
const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) :
|
||||||
m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
|
m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "wxExtensions.hpp"
|
#include "wxExtensions.hpp"
|
||||||
|
|
||||||
class wxBoxSizer;
|
class wxBoxSizer;
|
||||||
|
class wxBitmapComboBox;
|
||||||
class wxMenuItem;
|
class wxMenuItem;
|
||||||
class ObjectDataViewModel;
|
class ObjectDataViewModel;
|
||||||
class MenuWithSeparators;
|
class MenuWithSeparators;
|
||||||
|
|
@ -140,6 +141,8 @@ private:
|
||||||
DynamicPrintConfig *m_config {nullptr};
|
DynamicPrintConfig *m_config {nullptr};
|
||||||
std::vector<ModelObject*> *m_objects{ nullptr };
|
std::vector<ModelObject*> *m_objects{ nullptr };
|
||||||
|
|
||||||
|
wxBitmapComboBox *m_extruder_editor { nullptr };
|
||||||
|
|
||||||
std::vector<wxBitmap*> m_bmp_vector;
|
std::vector<wxBitmap*> m_bmp_vector;
|
||||||
|
|
||||||
t_layer_config_ranges m_layer_config_ranges_cache;
|
t_layer_config_ranges m_layer_config_ranges_cache;
|
||||||
|
|
@ -183,8 +186,8 @@ public:
|
||||||
|
|
||||||
void create_objects_ctrl();
|
void create_objects_ctrl();
|
||||||
void create_popup_menus();
|
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_objects_list_extruder_column(size_t extruders_count);
|
||||||
|
void update_extruder_colors();
|
||||||
// show/hide "Extruder" column for Objects List
|
// show/hide "Extruder" column for Objects List
|
||||||
void set_extruder_column_hidden(const bool hide) const;
|
void set_extruder_column_hidden(const bool hide) const;
|
||||||
// update extruder in current config
|
// update extruder in current config
|
||||||
|
|
@ -210,6 +213,7 @@ public:
|
||||||
|
|
||||||
void selection_changed();
|
void selection_changed();
|
||||||
void show_context_menu(const bool evt_context_menu);
|
void show_context_menu(const bool evt_context_menu);
|
||||||
|
void extruder_editing();
|
||||||
#ifndef __WXOSX__
|
#ifndef __WXOSX__
|
||||||
void key_event(wxKeyEvent& event);
|
void key_event(wxKeyEvent& event);
|
||||||
#endif /* __WXOSX__ */
|
#endif /* __WXOSX__ */
|
||||||
|
|
@ -233,7 +237,8 @@ public:
|
||||||
wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent);
|
wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent);
|
||||||
void append_menu_items_osx(wxMenu* menu);
|
void append_menu_items_osx(wxMenu* menu);
|
||||||
wxMenuItem* append_menu_item_fix_through_netfabb(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_change_extruder(wxMenu* menu) const;
|
||||||
void append_menu_item_delete(wxMenu* menu);
|
void append_menu_item_delete(wxMenu* menu);
|
||||||
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);
|
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);
|
||||||
|
|
|
||||||
|
|
@ -112,40 +112,33 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo)
|
||||||
combo->SetValue(selection);
|
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) :
|
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||||
OG_Settings(parent, true)
|
OG_Settings(parent, true)
|
||||||
#ifndef __APPLE__
|
|
||||||
, m_focused_option("")
|
|
||||||
#endif // __APPLE__
|
|
||||||
{
|
{
|
||||||
m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation");
|
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)
|
// Load bitmaps to be used for the mirroring buttons:
|
||||||
{
|
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
|
||||||
#ifndef __APPLE__
|
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off");
|
||||||
m_focused_option = opt_key;
|
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
|
||||||
#endif // __APPLE__
|
|
||||||
|
|
||||||
// needed to show the visual hints in 3D scene
|
const int border = wxOSX ? 0 : 4;
|
||||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true);
|
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" };
|
m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
|
||||||
|
if (is_windows10())
|
||||||
auto manifold_warning_icon = [this](wxWindow* parent) {
|
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 object/sub-object has no errors
|
||||||
if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData())
|
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());
|
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;
|
// Add labels
|
||||||
def.label = "";
|
add_label(&m_move_Label, L("Position"));
|
||||||
def.gui_type = "legend";
|
add_label(&m_rotate_Label, L("Rotation"));
|
||||||
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);
|
|
||||||
|
|
||||||
const int field_width = 5;
|
// additional sizer for lock and labels "Scale" & "Size"
|
||||||
|
sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
// Mirror button size:
|
m_lock_bnt = new LockButton(parent, wxID_ANY);
|
||||||
const int mirror_btn_width = 3;
|
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
|
auto v_sizer = new wxGridSizer(1, 3, 3);
|
||||||
line = Line{ "", "" };
|
|
||||||
def.label = "";
|
|
||||||
def.type = coString;
|
|
||||||
def.width = field_width - mirror_btn_width;//field_width/*50*/;
|
|
||||||
|
|
||||||
// Load bitmaps to be used for the mirroring buttons:
|
add_label(&m_scale_Label, L("Scale"), v_sizer);
|
||||||
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
|
wxStaticText* size_Label {nullptr};
|
||||||
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off");
|
add_label(&size_Label, L("Size"), v_sizer);
|
||||||
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
|
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' };
|
static const char axes[] = { 'X', 'Y', 'Z' };
|
||||||
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
|
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
|
||||||
const char label = 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:
|
// We will add a button to toggle mirroring to each axis:
|
||||||
auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) {
|
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
|
||||||
wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
|
btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
|
||||||
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
|
btn->SetBitmapDisabled_(m_mirror_bitmap_hidden);
|
||||||
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].first = btn;
|
||||||
m_mirror_buttons[axis_idx].second = mbShown;
|
m_mirror_buttons[axis_idx].second = mbShown;
|
||||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
||||||
sizer->Add(btn);
|
|
||||||
|
|
||||||
btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) {
|
sizer->AddStretchSpacer(2);
|
||||||
Axis axis = (Axis)(axis_idx + X);
|
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||||
if (m_mirror_buttons[axis_idx].second == mbHidden)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) {
|
||||||
Selection& selection = canvas->get_selection();
|
Axis axis = (Axis)(axis_idx + X);
|
||||||
|
if (m_mirror_buttons[axis_idx].second == mbHidden)
|
||||||
|
return;
|
||||||
|
|
||||||
if (selection.is_single_volume() || selection.is_single_modifier()) {
|
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
|
Selection& selection = canvas->get_selection();
|
||||||
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
|
|
||||||
|
if (selection.is_single_volume() || selection.is_single_modifier()) {
|
||||||
|
GLVolume* volume = const_cast<GLVolume*>(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<GLVolume*>(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()){
|
else
|
||||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
|
return;
|
||||||
volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Update mirroring at the GLVolumes.
|
// Update mirroring at the GLVolumes.
|
||||||
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
||||||
selection.synchronize_unselected_volumes();
|
selection.synchronize_unselected_volumes();
|
||||||
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
|
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
|
||||||
canvas->do_mirror(L("Set Mirror"));
|
canvas->do_mirror(L("Set Mirror"));
|
||||||
UpdateAndShow(true);
|
UpdateAndShow(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
return sizer;
|
editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL);
|
||||||
};
|
|
||||||
|
|
||||||
option.side_widget = mirror_button;
|
|
||||||
line.append_option(option);
|
|
||||||
}
|
}
|
||||||
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), "" };
|
ManipulationEditor* editor = new ManipulationEditor(this, opt_key, axis);
|
||||||
ConfigOptionDef def;
|
m_editors.push_back(editor);
|
||||||
def.type = coFloat;
|
|
||||||
def.set_default_value(new ConfigOptionFloat(0.0));
|
|
||||||
def.width = field_width/*50*/;
|
|
||||||
|
|
||||||
if (option_name == "Scale") {
|
editors_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL);
|
||||||
// Add "uniform scaling" button in front of "Scale" option
|
};
|
||||||
line.near_label_widget = [this](wxWindow* parent) {
|
|
||||||
auto btn = new LockButton(parent, wxID_ANY);
|
// add Units
|
||||||
btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){
|
auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit)
|
||||||
event.Skip();
|
{
|
||||||
wxTheApp->CallAfter([btn, this]() { set_uniform_scaling(btn->IsLocked()); });
|
wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
|
||||||
});
|
set_font_and_background_style(unit_text, wxGetApp().normal_font());
|
||||||
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();
|
|
||||||
|
|
||||||
if (selection.is_single_volume() || selection.is_single_modifier()) {
|
// Unit text should be the same height as labels
|
||||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
|
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
volume->set_volume_rotation(Vec3d::Zero());
|
sizer->SetMinSize(wxSize(-1, height));
|
||||||
}
|
sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL);
|
||||||
else if (selection.is_single_full_instance()) {
|
|
||||||
for (unsigned int idx : selection.get_volume_idxs()){
|
|
||||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
|
|
||||||
volume->set_instance_rotation(Vec3d::Zero());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Update rotation at the GLVolumes.
|
editors_grid_sizer->Add(sizer);
|
||||||
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
m_rescalable_sizers.push_back(sizer);
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Settings table
|
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
|
||||||
m_og->sidetext_width = 3;
|
add_edit_boxes("position", axis_idx);
|
||||||
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label);
|
add_unit_text(L("mm"));
|
||||||
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"));
|
|
||||||
|
|
||||||
// call back for a rescale of button "Set uniform scale"
|
// Add drop to bed button
|
||||||
m_og->rescale_near_label_widget = [this](wxWindow* win) {
|
m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
|
||||||
// rescale lock icon
|
m_drop_to_bed_button->SetToolTip(_(L("Drop to bed")));
|
||||||
auto *ctrl = dynamic_cast<LockButton*>(win);
|
m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
|
||||||
if (ctrl != nullptr) {
|
// ???
|
||||||
ctrl->msw_rescale();
|
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||||
return;
|
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<GLVolume*>(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<GLVolume*>(selection.get_volume(idx));
|
||||||
|
volume->set_instance_rotation(Vec3d::Zero());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// rescale "place" of the empty icon (to correct layout of the "Size" and "Scale")
|
// Update rotation at the GLVolumes.
|
||||||
if (dynamic_cast<wxStaticBitmap*>(win) != nullptr)
|
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
||||||
win->SetMinSize(create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize());
|
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)
|
void ObjectManipulation::Show(const bool show)
|
||||||
{
|
{
|
||||||
|
|
@ -407,9 +421,9 @@ void ObjectManipulation::Show(const bool show)
|
||||||
if (show && wxGetApp().get_mode() != comSimple) {
|
if (show && wxGetApp().get_mode() != comSimple) {
|
||||||
// Show the label and the name of the STL in simple mode only.
|
// Show the label and the name of the STL in simple mode only.
|
||||||
// Label "Name: "
|
// 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.
|
// 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.
|
// 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;
|
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_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) {
|
if (label_cache != new_label_localized) {
|
||||||
label_cache = new_label_localized;
|
label_cache = new_label_localized;
|
||||||
widget->SetLabel(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.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.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);
|
update_label(m_cache.scale_label_string, m_new_scale_label_string, m_scale_Label);
|
||||||
|
|
||||||
char axis[2] = "x";
|
enum ManipulationEditorKey
|
||||||
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) {
|
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);
|
wxString new_text = double_to_string(new_value(i), 2);
|
||||||
double new_rounded;
|
double new_rounded;
|
||||||
new_text.ToDouble(&new_rounded);
|
new_text.ToDouble(&new_rounded);
|
||||||
if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
|
if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
|
||||||
cached_rounded(i) = new_rounded;
|
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);
|
cached(i) = new_value(i);
|
||||||
};
|
};
|
||||||
update(m_cache.position, m_cache.position_rounded, "position_", m_new_position);
|
update(m_cache.position, m_cache.position_rounded, mePosition, m_new_position);
|
||||||
update(m_cache.scale, m_cache.scale_rounded, "scale_", m_new_scale);
|
update(m_cache.scale, m_cache.scale_rounded, meScale, m_new_scale);
|
||||||
update(m_cache.size, m_cache.size_rounded, "size_", m_new_size);
|
update(m_cache.size, m_cache.size_rounded, meSize, m_new_size);
|
||||||
update(m_cache.rotation, m_cache.rotation_rounded, "rotation_", m_new_rotation);
|
update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (selection.requires_uniform_scale()) {
|
if (selection.requires_uniform_scale()) {
|
||||||
m_lock_bnt->SetLock(true);
|
m_lock_bnt->SetLock(true);
|
||||||
m_lock_bnt->SetToolTip(_(L("You cannot use non-uniform scaling mode for multiple objects/parts selection")));
|
m_lock_bnt->SetToolTip(_(L("You cannot use non-uniform scaling mode for multiple objects/parts selection")));
|
||||||
|
|
@ -673,20 +698,18 @@ void ObjectManipulation::update_mirror_buttons_visibility()
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
void ObjectManipulation::emulate_kill_focus()
|
void ObjectManipulation::emulate_kill_focus()
|
||||||
{
|
{
|
||||||
if (m_focused_option.empty())
|
if (!m_focused_editor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// we need to use a copy because the value of m_focused_option is modified inside on_change() and on_fill_empty_value()
|
m_focused_editor->kill_focus(this);
|
||||||
std::string option = m_focused_option;
|
|
||||||
|
|
||||||
// see TextCtrl::propagate_value()
|
|
||||||
if (static_cast<wxTextCtrl*>(m_og->get_fieldc(option, 0)->getWindow())->GetValue().empty())
|
|
||||||
on_fill_empty_value(option);
|
|
||||||
else
|
|
||||||
on_change(option, 0);
|
|
||||||
}
|
}
|
||||||
#endif // __APPLE__
|
#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)
|
void ObjectManipulation::update_warning_icon_state(const wxString& tooltip)
|
||||||
{
|
{
|
||||||
m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
|
m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
|
||||||
|
|
@ -817,76 +840,21 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
|
||||||
wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale"));
|
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())
|
if (!m_cache.is_valid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int axis = opt_key.back() - 'x';
|
if (opt_key == "position")
|
||||||
double new_value = boost::any_cast<double>(m_og->get_value(opt_key));
|
|
||||||
|
|
||||||
if (boost::starts_with(opt_key, "position_"))
|
|
||||||
change_position_value(axis, new_value);
|
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);
|
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);
|
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);
|
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)
|
void ObjectManipulation::set_uniform_scaling(const bool new_value)
|
||||||
{
|
{
|
||||||
const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
|
const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||||
|
|
@ -923,7 +891,10 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
|
||||||
|
|
||||||
void ObjectManipulation::msw_rescale()
|
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);
|
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();
|
m_manifold_warning_bmp.msw_rescale();
|
||||||
|
|
||||||
const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText();
|
const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText();
|
||||||
|
|
@ -936,12 +907,120 @@ void ObjectManipulation::msw_rescale()
|
||||||
m_reset_scale_button->msw_rescale();
|
m_reset_scale_button->msw_rescale();
|
||||||
m_reset_rotation_button->msw_rescale();
|
m_reset_rotation_button->msw_rescale();
|
||||||
m_drop_to_bed_button->msw_rescale();
|
m_drop_to_bed_button->msw_rescale();
|
||||||
|
m_lock_bnt->msw_rescale();
|
||||||
|
|
||||||
for (int id = 0; id < 3; ++id)
|
for (int id = 0; id < 3; ++id)
|
||||||
m_mirror_buttons[id].first->msw_rescale();
|
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();
|
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 GUI
|
||||||
} //namespace Slic3r
|
} //namespace Slic3r
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,29 @@ namespace GUI {
|
||||||
|
|
||||||
class Selection;
|
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
|
class ObjectManipulation : public OG_Settings
|
||||||
{
|
{
|
||||||
struct Cache
|
struct Cache
|
||||||
|
|
@ -53,6 +76,9 @@ class ObjectManipulation : public OG_Settings
|
||||||
wxStaticText* m_scale_Label = nullptr;
|
wxStaticText* m_scale_Label = nullptr;
|
||||||
wxStaticText* m_rotate_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.
|
// Non-owning pointers to the reset buttons, so we can hide and show them.
|
||||||
ScalableButton* m_reset_scale_button = nullptr;
|
ScalableButton* m_reset_scale_button = nullptr;
|
||||||
ScalableButton* m_reset_rotation_button = nullptr;
|
ScalableButton* m_reset_rotation_button = nullptr;
|
||||||
|
|
@ -81,7 +107,7 @@ class ObjectManipulation : public OG_Settings
|
||||||
Vec3d m_new_rotation;
|
Vec3d m_new_rotation;
|
||||||
Vec3d m_new_scale;
|
Vec3d m_new_scale;
|
||||||
Vec3d m_new_size;
|
Vec3d m_new_size;
|
||||||
bool m_new_enabled;
|
bool m_new_enabled {true};
|
||||||
bool m_uniform_scale {true};
|
bool m_uniform_scale {true};
|
||||||
// Does the object manipulation panel work in World or Local coordinates?
|
// Does the object manipulation panel work in World or Local coordinates?
|
||||||
bool m_world_coordinates = true;
|
bool m_world_coordinates = true;
|
||||||
|
|
@ -92,10 +118,19 @@ class ObjectManipulation : public OG_Settings
|
||||||
wxStaticBitmap* m_fix_throught_netfab_bitmap;
|
wxStaticBitmap* m_fix_throught_netfab_bitmap;
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
// Currently focused option name (empty if none)
|
// Currently focused editor (nullptr if none)
|
||||||
std::string m_focused_option;
|
ManipulationEditor* m_focused_editor {nullptr};
|
||||||
#endif // __APPLE__
|
#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<wxBoxSizer*> m_rescalable_sizers;
|
||||||
|
|
||||||
|
std::vector<ManipulationEditor*> m_editors;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ObjectManipulation(wxWindow* parent);
|
ObjectManipulation(wxWindow* parent);
|
||||||
~ObjectManipulation() {}
|
~ObjectManipulation() {}
|
||||||
|
|
@ -122,8 +157,15 @@ public:
|
||||||
void emulate_kill_focus();
|
void emulate_kill_focus();
|
||||||
#endif // __APPLE__
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
void update_item_name(const wxString &item_name);
|
||||||
void update_warning_icon_state(const wxString& tooltip);
|
void update_warning_icon_state(const wxString& tooltip);
|
||||||
void msw_rescale();
|
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:
|
private:
|
||||||
void reset_settings_value();
|
void reset_settings_value();
|
||||||
|
|
@ -140,9 +182,6 @@ private:
|
||||||
void change_scale_value(int axis, double value);
|
void change_scale_value(int axis, double value);
|
||||||
void change_size_value(int axis, double value);
|
void change_size_value(int axis, double value);
|
||||||
void do_scale(int axis, const Vec3d &scale) const;
|
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,9 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
|
||||||
|
|
||||||
bool GLGizmosManager::on_mouse(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());
|
Point pos(evt.GetX(), evt.GetY());
|
||||||
Vec2d mouse_pos((double)evt.GetX(), (double)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())
|
else if (evt.MiddleUp())
|
||||||
m_mouse_capture.middle = false;
|
m_mouse_capture.middle = false;
|
||||||
else if (evt.RightUp())
|
else if (evt.RightUp())
|
||||||
|
{
|
||||||
m_mouse_capture.right = false;
|
m_mouse_capture.right = false;
|
||||||
|
if (pending_right_up)
|
||||||
|
{
|
||||||
|
pending_right_up = false;
|
||||||
|
processed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (evt.Dragging() && m_mouse_capture.any())
|
else if (evt.Dragging() && m_mouse_capture.any())
|
||||||
// if the button down was done on this toolbar, prevent from dragging into the scene
|
// if the button down was done on this toolbar, prevent from dragging into the scene
|
||||||
processed = true;
|
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))
|
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
|
// event was taken care of by the SlaSupports gizmo
|
||||||
processed = true;
|
processed = true;
|
||||||
|
}
|
||||||
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
|
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
|
||||||
// don't allow dragging objects with the Sla gizmo on
|
// don't allow dragging objects with the Sla gizmo on
|
||||||
processed = true;
|
processed = true;
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const
|
||||||
const PrintObjects& objects = m_plater->sla_print().objects();
|
const PrintObjects& objects = m_plater->sla_print().objects();
|
||||||
for (const SLAPrintObject* object : 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;
|
can_export = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/filesystem/operations.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
|
@ -251,11 +252,18 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 *
|
||||||
auto selected_item = this->GetSelection();
|
auto selected_item = this->GetSelection();
|
||||||
|
|
||||||
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
|
auto marker = reinterpret_cast<Marker>(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);
|
this->SetSelection(this->last_selected);
|
||||||
evt.StopPropagation();
|
evt.StopPropagation();
|
||||||
if (marker == LABEL_ITEM_CONFIG_WIZARD)
|
if (marker >= LABEL_ITEM_WIZARD_PRINTERS) {
|
||||||
wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); });
|
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 ||
|
} else if ( this->last_selected != selected_item ||
|
||||||
wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) {
|
wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) {
|
||||||
this->last_selected = selected_item;
|
this->last_selected = selected_item;
|
||||||
|
|
@ -521,12 +529,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
|
||||||
const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
|
const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
|
||||||
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
|
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
|
||||||
|
|
||||||
const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||||
std::vector<std::string> extruder_colours = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
|
|
||||||
const std::vector<std::string>& filament_colours = (wxGetApp().plater()->get_plater_config()->option<ConfigOptionStrings>("filament_colour"))->values;
|
|
||||||
for (size_t i=0; i<extruder_colours.size(); ++i)
|
|
||||||
if (extruder_colours[i] == "" && i < filament_colours.size())
|
|
||||||
extruder_colours[i] = filament_colours[i];
|
|
||||||
|
|
||||||
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
|
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
|
||||||
|
|
||||||
|
|
@ -1576,7 +1579,8 @@ struct Plater::priv
|
||||||
|
|
||||||
size_t count = 0; // To know how much space to reserve
|
size_t count = 0; // To know how much space to reserve
|
||||||
for (auto obj : model.objects) count += obj->instances.size();
|
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_selected.reserve(count + 1 /* for optional wti */);
|
||||||
m_unselected.reserve(count + 1 /* for optional wti */);
|
m_unselected.reserve(count + 1 /* for optional wti */);
|
||||||
}
|
}
|
||||||
|
|
@ -1590,11 +1594,12 @@ struct Plater::priv
|
||||||
// Set up arrange polygon for a ModelInstance and Wipe tower
|
// Set up arrange polygon for a ModelInstance and Wipe tower
|
||||||
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
|
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
|
||||||
ArrangePolygon ap = obj->get_arrange_polygon();
|
ArrangePolygon ap = obj->get_arrange_polygon();
|
||||||
ap.priority = 0;
|
ap.priority = 0;
|
||||||
ap.bed_idx = ap.translation.x() / bed_stride();
|
ap.bed_idx = ap.translation.x() / bed_stride();
|
||||||
ap.setter = [obj, this](const ArrangePolygon &p) {
|
ap.setter = [obj, this](const ArrangePolygon &p) {
|
||||||
if (p.is_arranged()) {
|
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);
|
obj->apply_arrange_result(t, p.rotation);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1625,7 +1630,8 @@ struct Plater::priv
|
||||||
obj_sel(model.objects.size(), nullptr);
|
obj_sel(model.objects.size(), nullptr);
|
||||||
|
|
||||||
for (auto &s : plater().get_selection().get_content())
|
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
|
// Go through the objects and check if inside the selection
|
||||||
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
||||||
|
|
@ -1635,7 +1641,8 @@ struct Plater::priv
|
||||||
std::vector<bool> inst_sel(mo->instances.size(), false);
|
std::vector<bool> inst_sel(mo->instances.size(), false);
|
||||||
|
|
||||||
if (instlist)
|
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) {
|
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||||
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
||||||
|
|
@ -1907,6 +1914,7 @@ struct Plater::priv
|
||||||
bool can_fix_through_netfabb() const;
|
bool can_fix_through_netfabb() const;
|
||||||
bool can_set_instance_to_object() const;
|
bool can_set_instance_to_object() const;
|
||||||
bool can_mirror() const;
|
bool can_mirror() const;
|
||||||
|
bool can_reload_from_disk() const;
|
||||||
|
|
||||||
void msw_rescale_object_menu();
|
void msw_rescale_object_menu();
|
||||||
|
|
||||||
|
|
@ -1943,7 +1951,6 @@ private:
|
||||||
* */
|
* */
|
||||||
std::string m_last_fff_printer_profile_name;
|
std::string m_last_fff_printer_profile_name;
|
||||||
std::string m_last_sla_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);
|
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
|
||||||
|
|
@ -2469,11 +2476,8 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
||||||
_(L("Object too large?")));
|
_(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();
|
update();
|
||||||
|
|
@ -2764,9 +2768,8 @@ void Plater::priv::ArrangeJob::process() {
|
||||||
try {
|
try {
|
||||||
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
|
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
|
||||||
[this, count](unsigned st) {
|
[this, count](unsigned st) {
|
||||||
if (st >
|
if (st > 0) // will not finalize after last one
|
||||||
0) // will not finalize after last one
|
update_status(int(count - st), arrangestr);
|
||||||
update_status(count - st, arrangestr);
|
|
||||||
},
|
},
|
||||||
[this]() { return was_canceled(); });
|
[this]() { return was_canceled(); });
|
||||||
} catch (std::exception & /*e*/) {
|
} catch (std::exception & /*e*/) {
|
||||||
|
|
@ -3098,88 +3101,110 @@ void Plater::priv::update_sla_scene()
|
||||||
|
|
||||||
void Plater::priv::reload_from_disk()
|
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 Selection& 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();
|
|
||||||
|
|
||||||
auto *object_orig = model.objects[obj_orig_idx];
|
if (selection.is_wipe_tower())
|
||||||
std::vector<fs::path> 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);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto idx : new_idxs)
|
// struct to hold selected ModelVolumes by their indices
|
||||||
|
struct SelectedVolume
|
||||||
{
|
{
|
||||||
ModelObject *object = model.objects[idx];
|
int object_idx;
|
||||||
object->config.apply(object_orig->config);
|
int volume_idx;
|
||||||
|
|
||||||
object->clear_instances();
|
// operators needed by std::algorithms
|
||||||
for (const ModelInstance *instance : object_orig->instances)
|
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<SelectedVolume> selected_volumes;
|
||||||
|
|
||||||
|
// collects selected ModelVolumes
|
||||||
|
const std::set<unsigned int>& 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<fs::path> 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);
|
new_model = Model::read_from_file(path, nullptr, true, false);
|
||||||
}
|
for (ModelObject* model_object : new_model.objects)
|
||||||
|
|
||||||
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++)
|
|
||||||
{
|
{
|
||||||
object->volumes[i]->config.apply(object_orig->volumes[i]->config);
|
model_object->center_around_origin();
|
||||||
object->volumes[i]->translate(offset);
|
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
|
model.adjust_min_z();
|
||||||
m_update_objects_list_on_loading = true;
|
|
||||||
|
|
||||||
// puts the new objects into the list
|
// update 3D scene
|
||||||
for (const auto idx : new_idxs)
|
update();
|
||||||
{
|
|
||||||
wxGetApp().obj_list()->add_object_to_list(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(obj_orig_idx);
|
|
||||||
|
|
||||||
// new GLVolumes have been created at this point, so update their printable state
|
// new GLVolumes have been created at this point, so update their printable state
|
||||||
for (size_t i = 0; i < model.objects.size(); ++i)
|
for (size_t i = 0; i < model.objects.size(); ++i)
|
||||||
{
|
{
|
||||||
view3D->get_canvas3d()->update_instance_printable_state_for_object(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*/)
|
void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
|
||||||
|
|
@ -3604,6 +3629,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")),
|
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);
|
[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);
|
sidebar->obj_list()->append_menu_item_export_stl(menu);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -3630,8 +3658,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);
|
wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q);
|
||||||
menu->AppendSeparator();
|
menu->AppendSeparator();
|
||||||
|
|
||||||
append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")),
|
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected object from disk")),
|
||||||
[this](wxCommandEvent&) { reload_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")),
|
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); });
|
[this](wxCommandEvent&) { q->export_stl(false, true); });
|
||||||
|
|
@ -3786,6 +3814,48 @@ bool Plater::priv::can_mirror() const
|
||||||
return get_selection().is_from_single_instance();
|
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<SelectedVolume> selected_volumes;
|
||||||
|
|
||||||
|
const Selection& selection = get_selection();
|
||||||
|
|
||||||
|
// collects selected ModelVolumes
|
||||||
|
const std::set<unsigned int>& 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<fs::path> 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)
|
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);
|
bool new_shape = bed.set_shape(shape, custom_texture, custom_model);
|
||||||
|
|
@ -4467,10 +4537,10 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||||
bool is_left_handed = object->is_left_handed();
|
bool is_left_handed = object->is_left_handed();
|
||||||
|
|
||||||
TriangleMesh pad_mesh;
|
TriangleMesh pad_mesh;
|
||||||
bool has_pad_mesh = object->has_mesh(slaposBasePool);
|
bool has_pad_mesh = object->has_mesh(slaposPad);
|
||||||
if (has_pad_mesh)
|
if (has_pad_mesh)
|
||||||
{
|
{
|
||||||
pad_mesh = object->get_mesh(slaposBasePool);
|
pad_mesh = object->get_mesh(slaposPad);
|
||||||
pad_mesh.transform(mesh_trafo_inv);
|
pad_mesh.transform(mesh_trafo_inv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4568,6 +4638,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
|
bool Plater::has_toolpaths_to_export() const
|
||||||
{
|
{
|
||||||
return p->preview->get_canvas3d()->has_toolpaths_to_export();
|
return p->preview->get_canvas3d()->has_toolpaths_to_export();
|
||||||
|
|
@ -4646,7 +4721,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error
|
||||||
// Otherwise calculate everything, but start with the provided object.
|
// Otherwise calculate everything, but start with the provided object.
|
||||||
if (!this->p->background_processing_enabled()) {
|
if (!this->p->background_processing_enabled()) {
|
||||||
task.single_model_instance_only = true;
|
task.single_model_instance_only = true;
|
||||||
task.to_object_step = slaposBasePool;
|
task.to_object_step = slaposPad;
|
||||||
}
|
}
|
||||||
this->p->background_process.set_task(task);
|
this->p->background_process.set_task(task);
|
||||||
// and let the background processing start.
|
// and let the background processing start.
|
||||||
|
|
@ -4790,6 +4865,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));
|
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
|
||||||
|
|
||||||
p->config->option<ConfigOptionStrings>(opt_key)->values = filament_colors;
|
p->config->option<ConfigOptionStrings>(opt_key)->values = filament_colors;
|
||||||
|
p->sidebar->obj_list()->update_extruder_colors();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4815,6 +4891,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||||
else if(opt_key == "extruder_colour") {
|
else if(opt_key == "extruder_colour") {
|
||||||
update_scheduled = true;
|
update_scheduled = true;
|
||||||
p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
|
p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
|
||||||
|
p->sidebar->obj_list()->update_extruder_colors();
|
||||||
} else if(opt_key == "max_print_height") {
|
} else if(opt_key == "max_print_height") {
|
||||||
update_scheduled = true;
|
update_scheduled = true;
|
||||||
}
|
}
|
||||||
|
|
@ -4863,8 +4940,10 @@ void Plater::force_filament_colors_update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update_scheduled)
|
if (update_scheduled) {
|
||||||
update();
|
update();
|
||||||
|
p->sidebar->obj_list()->update_extruder_colors();
|
||||||
|
}
|
||||||
|
|
||||||
if (p->main_frame->is_loaded())
|
if (p->main_frame->is_loaded())
|
||||||
this->p->schedule_background_process();
|
this->p->schedule_background_process();
|
||||||
|
|
@ -4891,6 +4970,22 @@ const DynamicPrintConfig* Plater::get_plater_config() const
|
||||||
return p->config;
|
return p->config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
|
||||||
|
{
|
||||||
|
const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
|
std::vector<std::string> extruder_colors;
|
||||||
|
if (!config->has("extruder_colour")) // in case of a SLA print
|
||||||
|
return extruder_colors;
|
||||||
|
|
||||||
|
extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
|
||||||
|
const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("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
|
wxString Plater::get_project_filename(const wxString& extension) const
|
||||||
{
|
{
|
||||||
return p->get_project_filename(extension);
|
return p->get_project_filename(extension);
|
||||||
|
|
@ -5093,6 +5188,7 @@ bool Plater::can_copy_to_clipboard() const
|
||||||
|
|
||||||
bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); }
|
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_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(); }
|
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::enter_gizmos_stack() { p->enter_gizmos_stack(); }
|
||||||
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
|
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,12 @@ public:
|
||||||
ScalableButton* edit_btn { nullptr };
|
ScalableButton* edit_btn { nullptr };
|
||||||
|
|
||||||
enum LabelItemType {
|
enum LabelItemType {
|
||||||
LABEL_ITEM_MARKER = 0x4d,
|
LABEL_ITEM_MARKER = 0xffffff01,
|
||||||
LABEL_ITEM_CONFIG_WIZARD = 0x4e
|
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);
|
void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER);
|
||||||
|
|
@ -183,6 +187,7 @@ public:
|
||||||
void export_stl(bool extended = false, bool selection_only = false);
|
void export_stl(bool extended = false, bool selection_only = false);
|
||||||
void export_amf();
|
void export_amf();
|
||||||
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
|
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
|
||||||
|
void reload_from_disk();
|
||||||
bool has_toolpaths_to_export() const;
|
bool has_toolpaths_to_export() const;
|
||||||
void export_toolpaths_to_obj() const;
|
void export_toolpaths_to_obj() const;
|
||||||
void reslice();
|
void reslice();
|
||||||
|
|
@ -215,6 +220,7 @@ public:
|
||||||
// On activating the parent window.
|
// On activating the parent window.
|
||||||
void on_activate();
|
void on_activate();
|
||||||
const DynamicPrintConfig* get_plater_config() const;
|
const DynamicPrintConfig* get_plater_config() const;
|
||||||
|
std::vector<std::string> get_extruder_colors_from_plater_config() const;
|
||||||
|
|
||||||
void update_object_menu();
|
void update_object_menu();
|
||||||
|
|
||||||
|
|
@ -248,6 +254,7 @@ public:
|
||||||
bool can_copy_to_clipboard() const;
|
bool can_copy_to_clipboard() const;
|
||||||
bool can_undo() const;
|
bool can_undo() const;
|
||||||
bool can_redo() const;
|
bool can_redo() const;
|
||||||
|
bool can_reload_from_disk() const;
|
||||||
|
|
||||||
void msw_rescale();
|
void msw_rescale();
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue