Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_git_3010

This commit is contained in:
Enrico Turri 2019-10-14 08:49:19 +02:00
commit 76c9ddfd3e
147 changed files with 80456 additions and 5413 deletions

View File

@ -37,7 +37,7 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
# Proposal for C++ unit tests and sandboxes
option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF)
option(SLIC3R_BUILD_TESTS "Build unit tests" OFF)
option(SLIC3R_BUILD_TESTS "Build unit tests" ON)
# Print out the SLIC3R_* cache options
get_cmake_property(_cache_vars CACHE_VARIABLES)

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

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

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

View File

@ -82,6 +82,29 @@ variants = default
technology = SLA
family = SL1
[default_filaments]
Generic PLA = 1
Generic PLA MMU2 = 1
Prusa PLA = 1
Prusa PLA MMU2 = 1
Prusament PLA = 1
Prusament PLA MMU2 = 1
[default_sla_materials]
Prusa Azure Blue Tough 0.05 = 1
Prusa Black Tough 0.05 = 1
Prusa Green Casting 0.05 = 1
Prusa Grey Tough 0.05 = 1
Prusa Maroon Tough 0.05 = 1
Prusa Orange Tough 0.025 = 1
Prusa Orange Tough 0.035 = 1
Prusa Orange Tough 0.05 = 1
Prusa Orange Tough 0.1 = 1
Prusa Pink Tough 0.05 = 1
Prusa Skin Tough 0.05 = 1
Prusa Transparent Red Tough 0.05 = 1
Prusa White Tough 0.05 = 1
# All presets starting with asterisk, for example *common*, are intermediate and they will
# not make it into the user interface.
@ -1128,6 +1151,7 @@ filament_density = 3.9
filament_colour = #804040
filament_max_volumetric_speed = 9
filament_notes = "List of materials tested with standard print settings:\n\nColorFabb bronzeFill\nColorFabb brassFill\nColorFabb steelFill\nColorFabb copperFill"
filament_vendor = ColorFabb
[filament:ColorFabb HT]
inherits = *PET*
@ -1145,11 +1169,13 @@ max_fan_speed = 20
min_fan_speed = 10
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode"
temperature = 270
filament_vendor = ColorFabb
[filament:ColorFabb PLA-PHA]
inherits = *PLA*
filament_cost = 55.5
filament_density = 1.24
filament_vendor = ColorFabb
[filament:ColorFabb woodFill]
inherits = *PLA*
@ -1163,6 +1189,7 @@ filament_max_volumetric_speed = 10
first_layer_temperature = 200
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 200
filament_vendor = ColorFabb
[filament:ColorFabb corkFill]
inherits = *PLA*
@ -1175,6 +1202,7 @@ filament_max_volumetric_speed = 6
first_layer_temperature = 220
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 220
filament_vendor = ColorFabb
[filament:ColorFabb XT]
inherits = *PET*
@ -1184,6 +1212,7 @@ filament_density = 1.27
first_layer_bed_temperature = 90
first_layer_temperature = 260
temperature = 270
filament_vendor = ColorFabb
[filament:ColorFabb XT-CF20]
inherits = *PET*
@ -1199,6 +1228,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{el
temperature = 260
filament_retract_length = nil
filament_retract_lift = 0.2
filament_vendor = ColorFabb
[filament:ColorFabb nGen]
inherits = *PET*
@ -1211,6 +1241,7 @@ filament_type = NGEN
first_layer_temperature = 240
max_fan_speed = 35
min_fan_speed = 20
filament_vendor = ColorFabb
[filament:ColorFabb nGen flex]
inherits = *FLEX*
@ -1231,12 +1262,14 @@ temperature = 260
filament_retract_length = nil
filament_retract_lift = 0
compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material)
filament_vendor = ColorFabb
[filament:E3D Edge]
inherits = *PET*
filament_cost = 56.9
filament_density = 1.26
filament_type = EDGE
filament_vendor = E3D
[filament:E3D PC-ABS]
inherits = *ABS*
@ -1245,6 +1278,7 @@ filament_type = PC
filament_density = 1.05
first_layer_temperature = 270
temperature = 270
filament_vendor = E3D
[filament:Fillamentum ABS]
inherits = *ABS*
@ -1252,6 +1286,7 @@ filament_cost = 32.4
filament_density = 1.04
first_layer_temperature = 240
temperature = 240
filament_vendor = Fillamentum
[filament:Fillamentum ASA]
inherits = *ABS*
@ -1266,6 +1301,7 @@ slowdown_below_layer_time = 15
first_layer_temperature = 265
temperature = 265
filament_type = ASA
filament_vendor = Fillamentum
[filament:Prusament ASA]
inherits = *ABS*
@ -1296,6 +1332,7 @@ first_layer_temperature = 275
max_fan_speed = 50
min_fan_speed = 50
temperature = 275
filament_vendor = Fillamentum
[filament:Fillamentum Timberfill]
inherits = *PLA*
@ -1309,24 +1346,28 @@ filament_max_volumetric_speed = 10
first_layer_temperature = 190
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 190
filament_vendor = Fillamentum
[filament:Generic ABS]
inherits = *ABS*
filament_cost = 27.82
filament_density = 1.04
filament_notes = "List of materials tested with standard ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS"
filament_vendor = Generic
[filament:Generic PET]
inherits = *PET*
filament_cost = 27.82
filament_density = 1.27
filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG"
filament_vendor = Generic
[filament:Generic PLA]
inherits = *PLA*
filament_cost = 25.4
filament_density = 1.24
filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH"
filament_vendor = Generic
[filament:Generic FLEX]
inherits = *FLEX*
@ -1347,6 +1388,7 @@ filament_colour = #3A80CA
first_layer_bed_temperature = 100
first_layer_temperature = 270
temperature = 270
filament_vendor = Polymaker
[filament:PrimaSelect PVA+]
inherits = *PLA*
@ -1363,12 +1405,14 @@ filament_type = PVA
first_layer_temperature = 195
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 195
filament_vendor = PrimaSelect
[filament:Prusa ABS]
inherits = *ABS*
filament_cost = 27.82
filament_density = 1.08
filament_notes = "List of materials tested with standard ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS"
filament_vendor = Prusa
[filament:*ABS MMU2*]
inherits = Prusa ABS
@ -1385,6 +1429,7 @@ filament_unloading_speed = 20
[filament:Generic ABS MMU2]
inherits = *ABS MMU2*
filament_vendor = Generic
[filament:Prusament ASA MMU2]
inherits = *ABS MMU2*
@ -1410,6 +1455,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{el
[filament:Prusa ABS MMU2]
inherits = *ABS MMU2*
filament_vendor = Prusa
[filament:Prusa HIPS]
inherits = *ABS*
@ -1428,6 +1474,7 @@ max_fan_speed = 20
min_fan_speed = 20
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 220
filament_vendor = Prusa
[filament:Prusa PET]
inherits = *PET*
@ -1435,6 +1482,7 @@ filament_cost = 27.82
filament_density = 1.27
filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG"
compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
filament_vendor = Prusa
[filament:Prusament PETG]
inherits = *PET*
@ -1444,12 +1492,14 @@ filament_cost = 24.99
filament_density = 1.27
filament_type = PETG
compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
filament_vendor = Prusa
[filament:Prusa PET 0.6 nozzle]
inherits = *PET06*
filament_cost = 27.82
filament_density = 1.27
filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG"
filament_vendor = Prusa
[filament:Prusament PETG 0.6 nozzle]
inherits = *PET06*
@ -1458,6 +1508,7 @@ temperature = 250
filament_cost = 24.99
filament_density = 1.27
filament_type = PETG
filament_vendor = Prusa
[filament:*PET MMU2*]
inherits = Prusa PET
@ -1485,9 +1536,11 @@ filament_max_volumetric_speed = 13
[filament:Generic PET MMU2]
inherits = *PET MMU2*
filament_vendor = Generic
[filament:Prusa PET MMU2]
inherits = *PET MMU2*
filament_vendor = Prusa
[filament:Prusament PETG MMU2]
inherits = *PET MMU2*
@ -1498,16 +1551,19 @@ inherits = *PET MMU2 06*
[filament:Prusa PET MMU2 0.6 nozzle]
inherits = *PET MMU2 06*
filament_vendor = Prusa
[filament:Prusament PETG MMU2 0.6 nozzle]
inherits = *PET MMU2 06*
filament_type = PETG
filament_vendor = Prusa
[filament:Prusa PLA]
inherits = *PLA*
filament_cost = 25.4
filament_density = 1.24
filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFiberlogy PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nAmazonBasics PLA"
filament_vendor = Prusa
[filament:Prusament PLA]
inherits = *PLA*
@ -1515,6 +1571,7 @@ temperature = 215
filament_cost = 24.99
filament_density = 1.24
filament_notes = "Affordable filament for everyday printing in premium quality manufactured in-house by Josef Prusa"
filament_vendor = Prusa
[filament:*PLA MMU2*]
inherits = Prusa PLA
@ -1534,18 +1591,22 @@ filament_unloading_speed_start = 100
[filament:Generic PLA MMU2]
inherits = *PLA MMU2*
filament_vendor = Generic
[filament:Prusa PLA MMU2]
inherits = *PLA MMU2*
filament_vendor = Prusa
[filament:Prusament PLA MMU2]
inherits = *PLA MMU2*
filament_vendor = Prusa
[filament:SemiFlex or Flexfill 98A]
inherits = *FLEX*
filament_cost = 82
filament_density = 1.22
filament_max_volumetric_speed = 1.35
filament_vendor = Flexfill
[filament:Taulman Bridge]
inherits = *common*
@ -1567,6 +1628,7 @@ max_fan_speed = 5
min_fan_speed = 0
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 250
filament_vendor = Taulman
[filament:Taulman T-Glase]
inherits = *PET*
@ -1580,6 +1642,7 @@ first_layer_temperature = 240
max_fan_speed = 5
min_fan_speed = 0
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
filament_vendor = Taulman
[filament:Verbatim BVOH]
inherits = *common*
@ -1603,6 +1666,7 @@ max_fan_speed = 100
min_fan_speed = 100
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 210
filament_vendor = Verbatim
[filament:Verbatim BVOH MMU2]
inherits = Verbatim BVOH
@ -1622,6 +1686,7 @@ filament_unload_time = 12
filament_unloading_speed = 20
filament_unloading_speed_start = 100
filament_loading_speed_start = 19
filament_vendor = Verbatim
[filament:PrimaSelect PVA+ MMU2]
inherits = *common*
@ -1660,6 +1725,7 @@ min_print_speed = 15
slowdown_below_layer_time = 20
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
temperature = 195
filament_vendor = PrimaSelect
[filament:Verbatim PP]
inherits = *common*
@ -1682,6 +1748,7 @@ max_fan_speed = 100
min_fan_speed = 100
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode"
temperature = 220
filament_vendor = Verbatim
## Filaments MMU1
@ -1899,9 +1966,11 @@ exposure_time = 6
initial_exposure_time = 40
[sla_material:BlueCast Keramaster Dental 0.025]
material_type = Dental
inherits = *common 0.025*
exposure_time = 6
initial_exposure_time = 45
material_vendor = Bluecast
[sla_material:BlueCast X10 0.025]
inherits = *common 0.025*
@ -1912,6 +1981,7 @@ initial_exposure_time = 100
inherits = *common 0.025*
exposure_time = 6
initial_exposure_time = 35
material_vendor = Prusa
[sla_material:Prusa Grey Tough 0.025]
inherits = *common 0.025*
@ -1964,31 +2034,38 @@ initial_exposure_time = 35
inherits = *common 0.05*
exposure_time = 7
initial_exposure_time = 35
material_vendor = Bluecast
[sla_material:BlueCast Keramaster 0.05]
inherits = *common 0.05*
exposure_time = 8
initial_exposure_time = 45
material_vendor = Bluecast
[sla_material:BlueCast Keramaster Dental 0.05]
material_type = Dental
inherits = *common 0.05*
exposure_time = 7
initial_exposure_time = 50
material_vendor = Bluecast
[sla_material:BlueCast LCD-DLP Original 0.05]
inherits = *common 0.05*
exposure_time = 10
initial_exposure_time = 60
material_vendor = Bluecast
[sla_material:BlueCast Phrozen Wax 0.05]
inherits = *common 0.05*
exposure_time = 16
initial_exposure_time = 50
material_vendor = Bluecast
[sla_material:BlueCast S+ 0.05]
inherits = *common 0.05*
exposure_time = 9
initial_exposure_time = 45
material_vendor = Bluecast
[sla_material:BlueCast X10 0.05]
inherits = *common 0.05*
@ -1999,26 +2076,31 @@ initial_exposure_time = 100
inherits = *common 0.05*
exposure_time = 6
initial_exposure_time = 40
material_vendor = Monocure
[sla_material:Monocure 3D Blue Rapid Resin 0.05]
inherits = *common 0.05*
exposure_time = 7
initial_exposure_time = 40
material_vendor = Monocure
[sla_material:Monocure 3D Clear Rapid Resin 0.05]
inherits = *common 0.05*
exposure_time = 8
initial_exposure_time = 40
material_vendor = Monocure
[sla_material:Monocure 3D Grey Rapid Resin 0.05]
inherits = *common 0.05*
exposure_time = 10
initial_exposure_time = 30
material_vendor = Monocure
[sla_material:Monocure 3D White Rapid Resin 0.05]
inherits = *common 0.05*
exposure_time = 7
initial_exposure_time = 40
material_vendor = Monocure
[sla_material:3DM-HTR140 (high temperature) 0.05]
inherits = *common 0.05*
@ -2034,36 +2116,43 @@ initial_exposure_time = 25
inherits = *common 0.05*
exposure_time = 20
initial_exposure_time = 40
material_vendor = 3DM
[sla_material:3DM-DENT 0.05]
inherits = *common 0.05*
exposure_time = 7
initial_exposure_time = 45
material_vendor = 3DM
[sla_material:3DM-HR Green 0.05]
inherits = *common 0.05*
exposure_time = 15
initial_exposure_time = 40
material_vendor = 3DM
[sla_material:3DM-HR Red Wine 0.05]
inherits = *common 0.05*
exposure_time = 9
initial_exposure_time = 35
material_vendor = 3DM
[sla_material:3DM-XPRO White 0.05]
inherits = *common 0.05*
exposure_time = 9
initial_exposure_time = 35
material_vendor = 3DM
[sla_material:FTD Ash Grey 0.05]
inherits = *common 0.05*
exposure_time = 9
initial_exposure_time = 40
material_vendor = FTD
[sla_material:Harz Labs Model Resin Cherry 0.05]
inherits = *common 0.05*
exposure_time = 8
initial_exposure_time = 45
material_vendor = Harz Labs
[sla_material:Photocentric Hard Grey 0.05]
inherits = *common 0.05*
@ -2116,6 +2205,7 @@ initial_exposure_time = 35
inherits = *common 0.05*
exposure_time = 13
initial_exposure_time = 40
material_vendor = Prusa
## [sla_material:Prusa Yellow Solid 0.05]
## inherits = *common 0.05*
@ -2126,6 +2216,7 @@ initial_exposure_time = 40
inherits = *common 0.05*
exposure_time = 7.5
initial_exposure_time = 35
material_vendor = Prusa
## [sla_material:Prusa Transparent Green Tough 0.05]
## inherits = *common 0.05*
@ -2136,21 +2227,25 @@ initial_exposure_time = 35
inherits = *common 0.05*
exposure_time = 6
initial_exposure_time = 35
material_vendor = Prusa
[sla_material:Prusa Maroon Tough 0.05]
inherits = *common 0.05*
exposure_time = 7.5
initial_exposure_time = 35
material_vendor = Prusa
[sla_material:Prusa Pink Tough 0.05]
inherits = *common 0.05*
exposure_time = 8
initial_exposure_time = 35
material_vendor = Prusa
[sla_material:Prusa Azure Blue Tough 0.05]
inherits = *common 0.05*
exposure_time = 8
initial_exposure_time = 35
material_vendor = Prusa
[sla_material:Prusa Transparent Tough 0.05]
inherits = *common 0.05*
@ -2193,6 +2288,7 @@ initial_exposure_time = 15
inherits = *common 0.035*
exposure_time = 6
initial_exposure_time = 35
material_vendor = Prusa
########### Materials 0.1
@ -2235,6 +2331,7 @@ initial_exposure_time = 55
inherits = *common 0.1*
exposure_time = 8
initial_exposure_time = 35
material_vendor = Prusa
[sla_material:Prusa Green Casting 0.1]
inherits = *common 0.1*

View File

@ -16,9 +16,9 @@ const std::string USAGE_STR = {
namespace Slic3r { namespace sla {
Contour3D create_base_pool(const Polygons &ground_layer,
Contour3D create_pad(const Polygons &ground_layer,
const ExPolygons &holes = {},
const PoolConfig& cfg = PoolConfig());
const PadConfig& cfg = PadConfig());
Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling,
double floor_z_mm, double ceiling_z_mm,
@ -45,7 +45,7 @@ int main(const int argc, const char *argv[]) {
model.align_to_origin();
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;
ground_slice = offset_ex(ground_slice, 0.5);
@ -56,10 +56,10 @@ int main(const int argc, const char *argv[]) {
bench.start();
sla::PoolConfig cfg;
sla::PadConfig cfg;
cfg.min_wall_height_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();

View File

@ -156,7 +156,7 @@ namespace agg
//-------------------------------------------------------------------
template<class VertexSource>
void add_path(VertexSource& vs, unsigned path_id=0)
void add_path(VertexSource &&vs, unsigned path_id=0)
{
double x;
double y;

View File

@ -13,11 +13,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED)
# Add our own cmake module path.
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded
and the provided unit tests will be included in the build." OFF)
option(LIBNEST2D_BUILD_EXAMPLES "If enabled, examples will be built." OFF)
option(LIBNEST2D_HEADER_ONLY "If enabled static library will not be built." ON)
set(GEOMETRY_BACKENDS clipper boost eigen)
@ -109,26 +104,3 @@ if(NOT LIBNEST2D_HEADER_ONLY)
target_link_libraries(${LIBNAME} PUBLIC libnest2d)
target_compile_definitions(${LIBNAME} PUBLIC LIBNEST2D_STATIC)
endif()
if(LIBNEST2D_BUILD_EXAMPLES)
add_executable(example examples/main.cpp
# tools/libnfpglue.hpp
# tools/libnfpglue.cpp
tools/nfp_svgnest.hpp
tools/nfp_svgnest_glue.hpp
tools/svgtools.hpp
tests/printer_parts.cpp
tests/printer_parts.h
)
if(NOT LIBNEST2D_HEADER_ONLY)
target_link_libraries(example ${LIBNAME})
else()
target_link_libraries(example libnest2d)
endif()
endif()
if(LIBNEST2D_UNITTESTS)
add_subdirectory(${PROJECT_SOURCE_DIR}/tests)
endif()

View File

@ -49,91 +49,84 @@ using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
extern template class Nester<NfpPlacer, 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);
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
extern template std::size_t Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
void nest(Iterator from, Iterator to,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
nester.execute(from, to);
}
template<class Placer = NfpPlacer, class Selector = FirstFitSelection>
struct NestConfig {
typename Placer::Config placer_config;
typename Selector::Config selector_config;
using Placement = typename Placer::Config;
using Selection = typename Selector::Config;
NestConfig() = default;
NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {}
NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {}
NestConfig(const typename Placer::Config & pcfg,
const typename Selector::Config &scfg)
: placer_config{pcfg}, selector_config{scfg} {}
};
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,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
void nest(Iterator from, Iterator to,
const typename Placer::BinType& bin,
ProgressFunction prg,
StopCondition scond = []() { return false; },
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
std::size_t nest(Iterator from, Iterator to,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf);
if(prg) nester.progressIndicator(prg);
if(scond) nester.stopCondition(scond);
nester.execute(from, to);
_Nester<Placer, Selector> nester{bin, dist, cfg.placer_config, cfg.selector_config};
if(ctl.progressfn) nester.progressIndicator(ctl.progressfn);
if(ctl.stopcond) nester.stopCondition(ctl.stopcond);
return nester.execute(from, to);
}
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template void nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
extern template void nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
NestControl ctl);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
void nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
std::size_t nest(Container&& cont,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, pconf, sconf);
}
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);
return nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, cfg, ctl);
}
}

View File

@ -129,7 +129,11 @@ public:
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
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 int binId() const noexcept { return binid_; }
@ -748,6 +752,7 @@ template<class PlacementStrategy, class SelectionStrategy >
class _Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
public:
using Item = typename PlacementStrategy::Item;
using ShapeType = typename Item::ShapeType;
@ -824,7 +829,7 @@ public:
* the selection algorithm.
*/
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));
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) {
item.inflate(-infl);
});
return selector_.getResult().size();
}
/// Set a progress indicator function object for the selector.

View File

@ -1122,8 +1122,6 @@ private:
sl::rotate(sh, item.rotation());
Box bb = sl::boundingBox(sh);
bb.minCorner() += item.translation();
bb.maxCorner() += item.translation();
Vertex ci, cb;
auto bbin = sl::boundingBox(bin_);

View File

@ -5,19 +5,17 @@ namespace libnest2d {
template class Nester<NfpPlacer, FirstFitSelection>;
template class Nester<BottomLeftPlacer, FirstFitSelection>;
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
NestControl ctl);
}

View File

@ -375,7 +375,7 @@ public:
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
Item& itm = fixeditems[idx];
itm.markAsFixed();
itm.markAsFixedInBin(0);
}
m_pck.configure(m_pconf);

View File

@ -15,7 +15,7 @@ public:
PointClass max;
bool defined;
BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {}
BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {}
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
@ -59,7 +59,7 @@ template <class PointClass>
class BoundingBox3Base : public BoundingBoxBase<PointClass>
{
public:
BoundingBox3Base() : BoundingBoxBase<PointClass>() {};
BoundingBox3Base() : BoundingBoxBase<PointClass>() {}
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
BoundingBoxBase<PointClass>(pmin, pmax)
{ 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>
{
public:
@ -113,9 +140,9 @@ public:
// to encompass the original bounding box.
void align_to_grid(const coord_t cell_size);
BoundingBox() : BoundingBoxBase<Point>() {};
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {};
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {};
BoundingBox() : BoundingBoxBase<Point>() {}
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
BoundingBox(const Lines &lines);
friend BoundingBox get_extents_rotated(const Points &points, double angle);
@ -124,25 +151,25 @@ public:
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
{
public:
BoundingBox3() : BoundingBox3Base<Vec3crd>() {};
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {};
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {};
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
};
class BoundingBoxf : public BoundingBoxBase<Vec2d>
{
public:
BoundingBoxf() : BoundingBoxBase<Vec2d>() {};
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {};
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {};
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
};
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
{
public:
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {};
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {};
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {};
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {}
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {}
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {}
BoundingBoxf3 transformed(const Transform3d& matrix) const;
};

View File

@ -6,9 +6,9 @@
namespace Slic3r {
BridgeDetector::BridgeDetector(
ExPolygon _expolygon,
const ExPolygonCollection &_lower_slices,
coord_t _spacing) :
ExPolygon _expolygon,
const ExPolygons &_lower_slices,
coord_t _spacing) :
// The original infill polygon, not inflated.
expolygons(expolygons_owned),
// All surfaces of the object supporting this region.
@ -20,9 +20,9 @@ BridgeDetector::BridgeDetector(
}
BridgeDetector::BridgeDetector(
const ExPolygons &_expolygons,
const ExPolygonCollection &_lower_slices,
coord_t _spacing) :
const ExPolygons &_expolygons,
const ExPolygons &_lower_slices,
coord_t _spacing) :
// The original infill polygon, not inflated.
expolygons(_expolygons),
// All surfaces of the object supporting this region.
@ -46,7 +46,11 @@ void BridgeDetector::initialize()
// Detect what edges lie on lower slices by turning bridge contour and holes
// into polylines and then clipping them with each lower slice's contour.
// Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()).
this->_edges = intersection_pl(to_polylines(grown), this->lower_slices.contours());
Polygons contours;
contours.reserve(this->lower_slices.size());
for (const ExPolygon &expoly : this->lower_slices)
contours.push_back(expoly.contour);
this->_edges = intersection_pl(to_polylines(grown), contours);
#ifdef SLIC3R_DEBUG
printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size());
@ -54,7 +58,7 @@ void BridgeDetector::initialize()
// detect anchors as intersection between our bridge expolygon and the lower slices
// safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges
this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices.expolygons), true);
this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices), true);
/*
if (0) {
@ -271,7 +275,7 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
if (angle == -1) angle = this->angle;
if (angle == -1) return;
Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing));
Polygons grown_lower = offset(this->lower_slices, float(this->spacing));
for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) {
// get unsupported bridge edges (both contour and holes)

View File

@ -3,7 +3,6 @@
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
#include <string>
namespace Slic3r {
@ -21,7 +20,7 @@ public:
// In case the caller gaves us the input polygons by a value, make a copy.
ExPolygons expolygons_owned;
// Lower slices, all regions.
const ExPolygonCollection &lower_slices;
const ExPolygons &lower_slices;
// Scaled extrusion width of the infill.
coord_t spacing;
// Angle resolution for the brute force search of the best bridging angle.
@ -29,8 +28,8 @@ public:
// The final optimal angle.
double angle;
BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_width);
BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_width);
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
bool detect_angle(double bridge_direction_override = 0.);
Polygons coverage(double angle = -1) const;

View File

@ -176,8 +176,13 @@ add_library(libslic3r STATIC
miniz_extension.cpp
SLA/SLACommon.hpp
SLA/SLABoilerPlate.hpp
SLA/SLABasePool.hpp
SLA/SLABasePool.cpp
SLA/SLAPad.hpp
SLA/SLAPad.cpp
SLA/SLASupportTreeBuilder.hpp
SLA/SLASupportTreeBuildsteps.hpp
SLA/SLASupportTreeBuildsteps.cpp
SLA/SLASupportTreeBuilder.cpp
SLA/SLAConcurrency.hpp
SLA/SLASupportTree.hpp
SLA/SLASupportTree.cpp
SLA/SLASupportTreeIGL.cpp
@ -189,6 +194,8 @@ add_library(libslic3r STATIC
SLA/SLARaster.cpp
SLA/SLARasterWriter.hpp
SLA/SLARasterWriter.cpp
SLA/ConcaveHull.hpp
SLA/ConcaveHull.cpp
)
encoding_check(libslic3r)
@ -215,6 +222,7 @@ target_link_libraries(libslic3r
qhull
semver
tbb
${CMAKE_DL_LIBS}
)
if(WIN32)

View File

@ -194,6 +194,19 @@ ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input)
return retval;
}
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input)
{
ClipperLib::Paths retval;
for (auto &ep : input) {
retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(ep.contour));
for (auto &h : ep.holes)
retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(h));
}
return retval;
}
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input)
{
ClipperLib::Paths retval;
@ -472,14 +485,16 @@ ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1,
return union_ex(polys);
}
template <class T>
T
_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject,
const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
template<class T, class TSubj, class TClip>
T _clipper_do(const ClipperLib::ClipType clipType,
TSubj && subject,
TClip && clip,
const ClipperLib::PolyFillType fillType,
const bool safety_offset_)
{
// read input
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward<TSubj>(subject));
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(std::forward<TClip>(clip));
// perform safety offset
if (safety_offset_) {
@ -648,12 +663,26 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons
return retval;
}
ClipperLib::PolyTree
union_pt(const Polygons &subject, bool safety_offset_)
ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_)
{
return _clipper_do<ClipperLib::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
union_pt_chained(const Polygons &subject, bool safety_offset_)
{
@ -664,28 +693,123 @@ union_pt_chained(const Polygons &subject, bool safety_offset_)
return retval;
}
void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval)
static ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes)
{
/* use a nearest neighbor search to order these children
TODO: supply start_near to chained_path() too? */
// collect ordering points
Points ordering_points;
ordering_points.reserve(nodes.size());
for (ClipperLib::PolyNode *pn : nodes)
ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y));
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
TODO: supply start_near to chained_path() too? */
// push results recursively
for (ClipperLib::PolyNode *pn : ordered_nodes) {
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
// traverse the next depth
traverse_pt(pn->Childs, retval);
retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour));
if (pn->IsHole())
retval->back().reverse(); // ccw
}
_traverse_pt<o>(node->Childs, retval);
retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour));
if (node->IsHole()) 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)

View File

@ -34,6 +34,7 @@ Slic3r::ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree);
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input);
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input);
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input);
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input);
Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input);
Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input);
@ -215,8 +216,19 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false);
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_offset_ = false);
ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false);
ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ = false);
Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false);
void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval);
void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval);
void traverse_pt(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval);
void traverse_pt(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval);
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::Polygons *retval);
void traverse_pt_unordered(const ClipperLib::PolyNodes &nodes, Slic3r::ExPolygons *retval);
void traverse_pt_unordered(const ClipperLib::PolyNode *tree, Slic3r::ExPolygons *retval);
/* OTHER */
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);

View File

@ -271,8 +271,6 @@ ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, Con
return def;
}
std::string ConfigOptionDef::nocli = "~~~noCLI";
std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
{
// prepare a function for wrapping text

View File

@ -1444,7 +1444,7 @@ public:
std::vector<std::string> cli_args(const std::string &key) const;
// Assign this key to cli to disable CLI for this option.
static std::string nocli;
static const constexpr char *nocli = "~~~noCLI";
};
// Map from a config option name to its definition.

View File

@ -24,6 +24,9 @@ public:
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; }
inline explicit ExPolygon(const Polygon &p): contour(p) {}
inline explicit ExPolygon(Polygon &&p): contour(std::move(p)) {}
Polygon contour;
Polygons holes;

View File

@ -11,7 +11,7 @@ ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
ExPolygonCollection::operator Points() const
{
Points points;
Polygons pp = *this;
Polygons pp = (Polygons)*this;
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
points.push_back(*point);

View File

@ -17,11 +17,11 @@ public:
ExPolygons expolygons;
ExPolygonCollection() {}
ExPolygonCollection(const ExPolygon &expolygon);
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
operator Points() const;
operator Polygons() const;
operator ExPolygons&();
explicit ExPolygonCollection(const ExPolygon &expolygon);
explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
explicit operator Points() const;
explicit operator Polygons() const;
explicit operator ExPolygons&();
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, const Point &center);

View File

@ -14,12 +14,12 @@ namespace Slic3r {
void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(intersection_pl(this->polyline, collection), retval);
this->_inflate_collection(intersection_pl(this->polyline, (Polygons)collection), retval);
}
void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(diff_pl(this->polyline, collection), retval);
this->_inflate_collection(diff_pl(this->polyline, (Polygons)collection), retval);
}
void ExtrusionPath::clip_end(double distance)

View File

@ -31,7 +31,8 @@ namespace pt = boost::property_tree;
// VERSION NUMBERS
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
const unsigned int VERSION_3MF = 1;
// 2 : Meshes saved in their local system; Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file.
const unsigned int VERSION_3MF = 2;
const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
const std::string MODEL_FOLDER = "3D/";
@ -87,6 +88,13 @@ const char* VOLUME_TYPE = "volume";
const char* NAME_KEY = "name";
const char* MODIFIER_KEY = "modifier";
const char* VOLUME_TYPE_KEY = "volume_type";
const char* MATRIX_KEY = "matrix";
const char* SOURCE_FILE_KEY = "source_file";
const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
const char* VALID_OBJECT_TYPES[] =
@ -148,11 +156,15 @@ bool get_attribute_value_bool(const char** attributes, unsigned int attributes_s
return (text != nullptr) ? (bool)::atoi(text) : true;
}
Slic3r::Transform3d get_transform_from_string(const std::string& mat_str)
Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str)
{
// check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md
// to see how matrices are stored inside 3mf according to specifications
Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
if (mat_str.empty())
// empty string means default identity matrix
return Slic3r::Transform3d::Identity();
return ret;
std::vector<std::string> mat_elements_str;
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();
if (size != 12)
// invalid data, return identity matrix
return Slic3r::Transform3d::Identity();
return ret;
Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
unsigned int i = 0;
// matrices are stored into 3mf files as 4x3
// we need to transpose them
@ -1375,7 +1386,7 @@ namespace Slic3r {
bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
{
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
if (object_item == m_objects.end())
@ -1421,7 +1432,7 @@ namespace Slic3r {
// see specifications
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
return _create_object_instance(object_id, transform, printable, 1);
@ -1634,6 +1645,21 @@ namespace Slic3r {
return false;
}
Slic3r::Geometry::Transformation transform;
if (m_version > 1)
{
// extract the volume transformation from the volume's metadata, if present
for (const Metadata& metadata : volume_data.metadata)
{
if (metadata.key == MATRIX_KEY)
{
transform.set_from_string(metadata.value);
break;
}
}
}
Transform3d inv_matrix = transform.get_matrix().inverse();
// splits volume out of imported geometry
TriangleMesh triangle_mesh;
stl_file &stl = triangle_mesh.stl;
@ -1651,7 +1677,12 @@ namespace Slic3r {
stl_facet& facet = stl.facet_start[i];
for (unsigned int v = 0; v < 3; ++v)
{
::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float));
unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3;
Vec3f vertex(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]);
if (m_version > 1)
// revert the vertices to the original mesh reference system
vertex = (inv_matrix * vertex.cast<double>()).cast<float>();
::memcpy(facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float));
}
}
@ -1659,10 +1690,12 @@ namespace Slic3r {
triangle_mesh.repair();
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
volume->center_geometry_after_creation();
// apply the volume matrix taken from the metadata, if present
if (m_version > 1)
volume->set_transformation(transform);
volume->calculate_convex_hull();
// apply volume's name and config data
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata)
{
if (metadata.key == NAME_KEY)
@ -1671,6 +1704,18 @@ namespace Slic3r {
volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
else if (metadata.key == VOLUME_TYPE_KEY)
volume->set_type(ModelVolume::type_from_string(metadata.value));
else if (metadata.key == SOURCE_FILE_KEY)
volume->source.input_file = metadata.value;
else if (metadata.key == SOURCE_OBJECT_ID_KEY)
volume->source.object_idx = ::atoi(metadata.value.c_str());
else if (metadata.key == SOURCE_VOLUME_ID_KEY)
volume->source.volume_idx = ::atoi(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_X_KEY)
volume->source.mesh_offset(0) = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_Y_KEY)
volume->source.mesh_offset(1) = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_Z_KEY)
volume->source.mesh_offset(2) = ::atof(metadata.value.c_str());
else
volume->config.set_deserialize(metadata.key, metadata.value);
}
@ -2116,7 +2161,7 @@ namespace Slic3r {
for (const BuildItem& item : build_items)
{
stream << " <" << ITEM_TAG << " objectid=\"" << item.id << "\" transform =\"";
stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\"";
for (unsigned c = 0; c < 4; ++c)
{
for (unsigned r = 0; r < 3; ++r)
@ -2126,7 +2171,7 @@ namespace Slic3r {
stream << " ";
}
}
stream << "\" printable =\"" << item.printable << "\" />\n";
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\" />\n";
}
stream << " </" << BUILD_TAG << ">\n";
@ -2344,6 +2389,31 @@ namespace Slic3r {
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
// stores volume's local matrix
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\"";
const Transform3d& matrix = volume->get_matrix();
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
stream << matrix(r, c);
if ((r != 3) || (c != 3))
stream << " ";
}
}
stream << "\"/>\n";
// stores volume's source data
if (!volume->source.input_file.empty())
{
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
}
// stores volume's config data
for (const std::string& key : volume->config.keys())
{

View File

@ -12,6 +12,7 @@
#include "../PrintConfig.hpp"
#include "../Utils.hpp"
#include "../I18N.hpp"
#include "../Geometry.hpp"
#include "AMF.hpp"
@ -36,7 +37,8 @@
// Added x and y components of rotation
// Added x, y and z components of scale
// Added x, y and z components of mirror
const unsigned int VERSION_AMF = 2;
// 3 : Meshes saved in their local system; Added volumes' matrices and source data
const unsigned int VERSION_AMF = 3;
const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
@ -560,15 +562,30 @@ void AMFParserContext::endElement(const char * /* name */)
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
Slic3r::Geometry::Transformation transform;
if (m_version > 2)
transform = m_volume->get_transformation();
Transform3d inv_matrix = transform.get_matrix().inverse();
for (size_t i = 0; i < m_volume_facets.size();) {
stl_facet &facet = stl.facet_start[i/3];
for (unsigned int v = 0; v < 3; ++ v)
memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
for (unsigned int v = 0; v < 3; ++v)
{
unsigned int tri_id = m_volume_facets[i++] * 3;
Vec3f vertex(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]);
if (m_version > 2)
// revert the vertices to the original mesh reference system
vertex = (inv_matrix * vertex.cast<double>()).cast<float>();
::memcpy((void*)facet.vertex[v].data(), (const void*)vertex.data(), 3 * sizeof(float));
}
}
stl_get_size(&stl);
mesh.repair();
m_volume->set_mesh(std::move(mesh));
m_volume->center_geometry_after_creation();
// pass false if the mesh offset has been already taken from the data
m_volume->center_geometry_after_creation(m_volume->source.input_file.empty());
m_volume->calculate_convex_hull();
m_volume_facets.clear();
m_volume = nullptr;
@ -664,6 +681,29 @@ void AMFParserContext::endElement(const char * /* name */)
} else if (strcmp(opt_key, "volume_type") == 0) {
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
else if (strcmp(opt_key, "matrix") == 0) {
Geometry::Transformation transform;
transform.set_from_string(m_value[1]);
m_volume->set_transformation(transform);
}
else if (strcmp(opt_key, "source_file") == 0) {
m_volume->source.input_file = m_value[1];
}
else if (strcmp(opt_key, "source_object_id") == 0) {
m_volume->source.object_idx = ::atoi(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_volume_id") == 0) {
m_volume->source.volume_idx = ::atoi(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_x") == 0) {
m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_y") == 0) {
m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_z") == 0) {
m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str());
}
}
} else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) {
@ -1057,7 +1097,28 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</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) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)

View File

@ -15,39 +15,41 @@
namespace Slic3r {
bool load_obj(const char *path, Model *model, const char *object_name_in)
bool load_obj(const char *path, TriangleMesh *meshptr)
{
if(meshptr == nullptr) return false;
// Parse the OBJ file.
ObjParser::ObjData data;
if (! ObjParser::objparse(path, data)) {
// die "Failed to parse $file\n" if !-e $path;
// die "Failed to parse $file\n" if !-e $path;
return false;
}
// Count the faces and verify, that all faces are triangular.
size_t num_faces = 0;
size_t num_quads = 0;
size_t num_quads = 0;
for (size_t i = 0; i < data.vertices.size(); ) {
size_t j = i;
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
if (i == j)
continue;
size_t face_vertices = j - i;
if (face_vertices != 3 && face_vertices != 4) {
size_t face_vertices = j - i;
if (face_vertices != 3 && face_vertices != 4) {
// Non-triangular and non-quad faces are not supported as of now.
return false;
}
if (face_vertices == 4)
++ num_quads;
++ num_faces;
if (face_vertices == 4)
++ num_quads;
++ num_faces;
i = j + 1;
}
// Convert ObjData into STL.
TriangleMesh mesh;
TriangleMesh &mesh = *meshptr;
stl_file &stl = mesh.stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = int(num_faces + num_quads);
stl.stats.number_of_facets = uint32_t(num_faces + num_quads);
stl.stats.original_num_facets = int(num_faces + num_quads);
// stl_allocate clears all the allocated data to zero, all normals are set to zeros as well.
stl_allocate(&stl);
@ -68,14 +70,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
++ num_normals;
}
}
if (data.vertices[i].coordIdx != -1) {
// This is a quad. Produce the other triangle.
stl_facet &facet2 = stl.facet_start[i_face++];
if (data.vertices[i].coordIdx != -1) {
// This is a quad. Produce the other triangle.
stl_facet &facet2 = stl.facet_start[i_face++];
facet2.vertex[0] = facet.vertex[0];
facet2.vertex[1] = facet.vertex[2];
const ObjParser::ObjVertex &vertex = data.vertices[i++];
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
if (vertex.normalIdx != -1) {
const ObjParser::ObjVertex &vertex = data.vertices[i++];
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
if (vertex.normalIdx != -1) {
normal(0) += data.normals[vertex.normalIdx*3];
normal(1) += data.normals[vertex.normalIdx*3+1];
normal(2) += data.normals[vertex.normalIdx*3+2];
@ -96,25 +98,37 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
if (len > EPSILON)
facet.normal = normal / len;
}
}
}
stl_get_size(&stl);
mesh.repair();
if (mesh.facets_count() == 0) {
// die "This STL file couldn't be read because it's empty.\n"
// die "This OBJ file couldn't be read because it's empty.\n"
return false;
}
std::string object_name;
if (object_name_in == nullptr) {
const char *last_slash = strrchr(path, DIR_SEPARATOR);
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
} else
object_name.assign(object_name_in);
model->add_object(object_name.c_str(), path, std::move(mesh));
return true;
}
bool load_obj(const char *path, Model *model, const char *object_name_in)
{
TriangleMesh mesh;
bool ret = load_obj(path, &mesh);
if (ret) {
std::string object_name;
if (object_name_in == nullptr) {
const char *last_slash = strrchr(path, DIR_SEPARATOR);
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
} else
object_name.assign(object_name_in);
model->add_object(object_name.c_str(), path, std::move(mesh));
}
return ret;
}
bool store_obj(const char *path, TriangleMesh *mesh)
{
//FIXME returning false even if write failed.

View File

@ -5,8 +5,10 @@ namespace Slic3r {
class TriangleMesh;
class Model;
class ModelObject;
// Load an OBJ file into a provided model.
extern bool load_obj(const char *path, TriangleMesh *mesh);
extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr);
extern bool store_obj(const char *path, TriangleMesh *mesh);

View File

@ -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 gcode;
assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size());
assert(m_layer_idx >= 0);
if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
if (m_layer_idx < (int)m_tool_changes.size()) {
if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))

View File

@ -138,7 +138,7 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
// We need to get position and angle of the wipe tower to transform them to actual position.
Transform2d trafo =
Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) *
Eigen::Rotation2Dd(print.config().wipe_tower_rotation_angle.value);
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {

View File

@ -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.
// Extrude 4 rounds of a brim around the future wipe tower.
box_coordinates box(wipeTower_box);
// the brim shall have 'normal' spacing with no extra void space
float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4);
for (size_t i = 0; i < 4; ++ i) {
box.expand(m_perimeter_width - m_layer_height*float(1.-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space
box.expand(spacing);
writer.travel (box.ld, 7000)
.extrude(box.lu, 2100).extrude(box.ru)
.extrude(box.rd ).extrude(box.ld);
@ -800,6 +802,10 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of
writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n"
";-----------------------------------\n");
// Save actual brim width to be later passed to the Print object, which will use it
// for skirt calculation and pass it to GLCanvas for precise preview box
m_wipe_tower_brim_width = wipeTower_box.ld.x() - box.ld.x() + spacing/2.f;
m_print_brim = false; // Mark the brim as extruded
// Ask our writer about how much material was consumed:

View File

@ -92,6 +92,7 @@ public:
void generate(std::vector<std::vector<ToolChangeResult>> &result);
float get_depth() const { return m_wipe_tower_depth; }
float get_brim_width() const { return m_wipe_tower_brim_width; }
@ -203,6 +204,7 @@ private:
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
float m_wipe_tower_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
float m_wipe_tower_brim_width = 0.f; // Width of brim (mm)
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
float m_internal_rotation = 0.f;
float m_y_shift = 0.f; // y shift passed to writer

View File

@ -14,6 +14,9 @@
#include <stack>
#include <vector>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#ifdef SLIC3R_DEBUG
#include "SVG.hpp"
#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;
}
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()
{
m_offset = Vec3d::Zero();

View File

@ -14,6 +14,11 @@
using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;
namespace ClipperLib {
class PolyNode;
using PolyNodes = std::vector<PolyNode*>;
}
namespace Slic3r { namespace Geometry {
// Generic result of an orientation predicate.
@ -275,6 +280,7 @@ public:
void set_mirror(Axis axis, double mirror);
void set_from_transform(const Transform3d& transform);
void set_from_string(const std::string& transform_str);
void reset();

View File

@ -252,22 +252,15 @@ template<class T> struct remove_cvref
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>;
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
template<class T, class I, template<class> class C = DefaultContainer>
inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n)
template<class T, class I, template<class> class Container = DefaultContainer>
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;
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
/// leaving 'stride' space between each value.
/// Very similar to Matlab [start:stride:end] notation.
template<class T, template<class> class C = DefaultContainer>
inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride)
template<class T, template<class> class Container = DefaultContainer>
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;
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;
}
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;
ret.reserve(capacity);
std::vector<T, Args...> ret;
if (capacity > I(0)) ret.reserve(size_t(capacity));
return ret;
}

View File

@ -141,12 +141,12 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
for (ModelObject *o : model.objects)
{
if (boost::algorithm::iends_with(input_file, ".zip.amf"))
{
// we remove the .zip part of the extension to avoid it be added to filenames when exporting
o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
}
else
// if (boost::algorithm::iends_with(input_file, ".zip.amf"))
// {
// // we remove the .zip part of the extension to avoid it be added to filenames when exporting
// o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
// }
// else
o->input_file = input_file;
}
@ -170,6 +170,9 @@ ModelObject* Model::add_object(const char *name, const char *path, const Triangl
new_object->input_file = path;
ModelVolume *new_volume = new_object->add_volume(mesh);
new_volume->name = name;
new_volume->source.input_file = path;
new_volume->source.object_idx = (int)this->objects.size() - 1;
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
new_object->invalidate_bounding_box();
return new_object;
}
@ -182,6 +185,9 @@ ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh
new_object->input_file = path;
ModelVolume *new_volume = new_object->add_volume(std::move(mesh));
new_volume->name = name;
new_volume->source.input_file = path;
new_volume->source.object_idx = (int)this->objects.size() - 1;
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
new_object->invalidate_bounding_box();
return new_object;
}
@ -1543,7 +1549,7 @@ bool ModelVolume::is_splittable() const
return m_is_splittable == 1;
}
void ModelVolume::center_geometry_after_creation()
void ModelVolume::center_geometry_after_creation(bool update_source_offset)
{
Vec3d shift = this->mesh().bounding_box().center();
if (!shift.isApprox(Vec3d::Zero()))
@ -1554,6 +1560,9 @@ void ModelVolume::center_geometry_after_creation()
const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
translate(shift);
}
if (update_source_offset)
source.mesh_offset = shift;
}
void ModelVolume::calculate_convex_hull()

View File

@ -392,6 +392,18 @@ class ModelVolume final : public ObjectBase
{
public:
std::string name;
// struct used by reload from disk command to recover data from disk
struct Source
{
std::string input_file;
int object_idx{ -1 };
int volume_idx{ -1 };
Vec3d mesh_offset{ Vec3d::Zero() };
template<class Archive> void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset); }
};
Source source;
// The triangular model.
const TriangleMesh& mesh() const { return *m_mesh.get(); }
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<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.
// Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
void center_geometry_after_creation();
void center_geometry_after_creation(bool update_source_offset = true);
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
@ -529,7 +541,7 @@ private:
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other) :
ObjectBase(other),
name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
@ -537,7 +549,7 @@ private:
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() != other.id() && this->config.id() == other.config.id());
@ -558,8 +570,8 @@ private:
}
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::load_by_value(ar, config);
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
cereal::load_optional(ar, m_convex_hull);
@ -571,8 +583,8 @@ private:
}
template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::save_by_value(ar, config);
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
}

View File

@ -136,11 +136,11 @@ Polyline MotionPlanner::shortest_path(const Point &from, const Point &to)
if (! grown_env.contains(from)) {
// delete second point while the line connecting first to third crosses the
// boundaries as many times as the current first to second
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), grown_env).size() == 1)
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1)
polyline.points.erase(polyline.points.begin() + 1);
}
if (! grown_env.contains(to))
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), grown_env).size() == 1)
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1)
polyline.points.erase(polyline.points.end() - 2);
}

View File

@ -143,10 +143,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"use_relative_e_distances",
"use_volumetric_e",
"variable_layer_height",
"wipe",
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle"
"wipe"
};
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_distance"
|| 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);
} else if (opt_key == "brim_width") {
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 == "z_offset") {
steps.emplace_back(psWipeTower);
steps.emplace_back(psSkirt);
} else if (
opt_key == "first_layer_extrusion_width"
|| 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).");
if (m_config.ooze_prevention)
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
if (m_config.use_volumetric_e)
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
if (m_objects.size() > 1) {
bool has_custom_layering = false;
@ -1502,6 +1505,14 @@ void Print::process()
obj->infill();
for (PrintObject *obj : m_objects)
obj->generate_support_material();
if (this->set_started(psWipeTower)) {
m_wipe_tower_data.clear();
if (this->has_wipe_tower()) {
//this->set_status(95, L("Generating wipe tower"));
this->_make_wipe_tower();
}
this->set_done(psWipeTower);
}
if (this->set_started(psSkirt)) {
m_skirt.clear();
if (this->has_skirt()) {
@ -1518,14 +1529,6 @@ void Print::process()
}
this->set_done(psBrim);
}
if (this->set_started(psWipeTower)) {
m_wipe_tower_data.clear();
if (this->has_wipe_tower()) {
//this->set_status(95, L("Generating wipe tower"));
this->_make_wipe_tower();
}
this->set_done(psWipeTower);
}
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
}
@ -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)
// At least three points required for a convex hull.
return;
@ -1864,6 +1878,22 @@ bool Print::has_wipe_tower() const
m_config.nozzle_diameter.values.size() > 1;
}
const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_layer_height, double nozzle_diameter) const
{
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
if (! is_step_done(psWipeTower) && extruders_cnt !=0) {
float width = m_config.wipe_tower_width;
float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4);
const_cast<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()
{
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());
wipe_tower.generate(m_wipe_tower_data.tool_changes);
m_wipe_tower_data.depth = wipe_tower.get_depth();
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;

View File

@ -226,6 +226,7 @@ struct WipeTowerData
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float depth;
float brim_width;
void clear() {
tool_ordering.clear();
@ -235,6 +236,7 @@ struct WipeTowerData
used_filament.clear();
number_of_toolchanges = -1;
depth = 0.f;
brim_width = 0.f;
}
};
@ -314,7 +316,6 @@ public:
bool has_infinite_skirt() const;
bool has_skirt() const;
float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; }
// Returns an empty string if valid, otherwise returns an error message.
std::string validate() const override;
@ -353,7 +354,7 @@ public:
// Wipe tower support.
bool has_wipe_tower() const;
const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; }
const WipeTowerData& wipe_tower_data(size_t extruders_cnt = 0, double first_layer_height = 0., double nozzle_diameter = 0.) const;
std::string output_filename(const std::string &filename_base = std::string()) const override;

View File

@ -749,6 +749,10 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionStrings { "" });
def->cli = ConfigOptionDef::nocli;
def = this->add("filament_vendor", coString);
def->set_default_value(new ConfigOptionString(L("(Unknown)")));
def->cli = ConfigOptionDef::nocli;
def = this->add("fill_angle", coFloat);
def->label = L("Fill angle");
def->category = L("Infill");
@ -2398,6 +2402,18 @@ void PrintConfigDef::init_sla_params()
// SLA Material settings.
def = this->add("material_type", coString);
def->label = L("SLA material type");
def->tooltip = L("SLA material type");
def->gui_type = "f_enum_open"; // TODO: ???
def->gui_flags = "show_value";
def->enum_values.push_back("Tough");
def->enum_values.push_back("Flexible");
def->enum_values.push_back("Casting");
def->enum_values.push_back("Dental");
def->enum_values.push_back("Heat-resistant");
def->set_default_value(new ConfigOptionString("Tough"));
def = this->add("initial_layer_height", coFloat);
def->label = L("Initial layer height");
def->tooltip = L("Initial layer height");
@ -2475,6 +2491,10 @@ void PrintConfigDef::init_sla_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("material_vendor", coString);
def->set_default_value(new ConfigOptionString(L("(Unknown)")));
def->cli = ConfigOptionDef::nocli;
def = this->add("default_sla_material_profile", coString);
def->label = L("Default SLA material profile");
def->tooltip = L("Default print profile associated with the current printer profile. "
@ -2695,6 +2715,17 @@ void PrintConfigDef::init_sla_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("pad_brim_size", coFloat);
def->label = L("Pad brim size");
def->tooltip = L("How far should the pad extend around the contained geometry");
def->category = L("Pad");
// def->tooltip = L("");
def->sidetext = L("mm");
def->min = 0;
def->max = 30;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.6));
def = this->add("pad_max_merge_distance", coFloat);
def->label = L("Max merge distance");
def->category = L("Pad");
@ -2735,6 +2766,13 @@ void PrintConfigDef::init_sla_params()
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("pad_around_object_everywhere", coBool);
def->label = L("Pad around object everywhere");
def->category = L("Pad");
def->tooltip = L("Force pad around object everywhere");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("pad_object_gap", coFloat);
def->label = L("Pad object gap");
def->category = L("Pad");

View File

@ -52,6 +52,14 @@ enum FilamentType {
};
*/
enum SLAMaterial {
slamTough,
slamFlex,
slamCasting,
slamDental,
slamHeatResistant,
};
enum SLADisplayOrientation {
sladoLandscape,
sladoPortrait
@ -1023,6 +1031,9 @@ public:
// The height of the pad from the bottom to the top not considering the pit
ConfigOptionFloat pad_wall_height /*= 5*/;
// How far should the pad extend around the contained geometry
ConfigOptionFloat pad_brim_size;
// The greatest distance where two individual pads are merged into one. The
// distance is measured roughly from the centroids of the pads.
ConfigOptionFloat pad_max_merge_distance /*= 50*/;
@ -1042,7 +1053,9 @@ public:
// /////////////////////////////////////////////////////////////////////////
// Disable the elevation (ignore its value) and use the zero elevation mode
ConfigOptionBool pad_around_object;
ConfigOptionBool pad_around_object;
ConfigOptionBool pad_around_object_everywhere;
// This is the gap between the object bottom and the generated pad
ConfigOptionFloat pad_object_gap;
@ -1082,10 +1095,12 @@ protected:
OPT_PTR(pad_enable);
OPT_PTR(pad_wall_thickness);
OPT_PTR(pad_wall_height);
OPT_PTR(pad_brim_size);
OPT_PTR(pad_max_merge_distance);
// OPT_PTR(pad_edge_radius);
OPT_PTR(pad_wall_slope);
OPT_PTR(pad_around_object);
OPT_PTR(pad_around_object_everywhere);
OPT_PTR(pad_object_gap);
OPT_PTR(pad_object_connector_stride);
OPT_PTR(pad_object_connector_width);

View 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 &centroids,
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

View 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 &centroids,
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

View File

@ -16,6 +16,7 @@
#include <random>
namespace Slic3r {
namespace sla {
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
{
@ -48,9 +49,16 @@ float SLAAutoSupports::distance_limit(float angle) const
return 1./(2.4*get_required_density(angle));
}*/
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, 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)
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
const std::vector<ExPolygons> &slices,
const std::vector<float> & heights,
const Config & config,
std::function<void(void)> throw_on_cancel,
std::function<void(int)> statusfn)
: m_config(config)
, m_emesh(emesh)
, m_throw_on_cancel(throw_on_cancel)
, m_statusfn(statusfn)
{
process(slices, heights);
project_onto_mesh(m_output);
@ -505,6 +513,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
}
}
void remove_bottom_points(std::vector<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
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
{
@ -533,4 +556,5 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::st
}
#endif
} // namespace sla
} // namespace Slic3r

View File

@ -11,20 +11,22 @@
// #define SLA_AUTOSUPPORTS_DEBUG
namespace Slic3r {
namespace sla {
class SLAAutoSupports {
public:
struct Config {
float density_relative;
float minimal_distance;
float head_diameter;
float density_relative {1.f};
float minimal_distance {1.f};
float head_diameter {0.4f};
///////////////
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
};
SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<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<sla::SupportPoint>& output() { return m_output; }
struct MyLayer;
@ -199,7 +201,9 @@ private:
std::function<void(int)> m_statusfn;
};
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
} // namespace sla
} // namespace Slic3r

View File

@ -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),
[&centroids, &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)));
}
}
}

View File

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

View File

@ -8,35 +8,19 @@
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include "SLACommon.hpp"
#include "SLASpatIndex.hpp"
namespace Slic3r {
namespace sla {
/// Get x and y coordinates (because we are eigenizing...)
inline coord_t x(const Point& p) { return p(0); }
inline coord_t y(const Point& p) { return p(1); }
inline coord_t& x(Point& p) { return p(0); }
inline coord_t& y(Point& p) { return p(1); }
inline coordf_t x(const Vec3d& p) { return p(0); }
inline coordf_t y(const Vec3d& p) { return p(1); }
inline coordf_t z(const Vec3d& p) { return p(2); }
inline coordf_t& x(Vec3d& p) { return p(0); }
inline coordf_t& y(Vec3d& p) { return p(1); }
inline coordf_t& z(Vec3d& p) { return p(2); }
inline coord_t& x(Vec3crd& p) { return p(0); }
inline coord_t& y(Vec3crd& p) { return p(1); }
inline coord_t& z(Vec3crd& p) { return p(2); }
inline coord_t x(const Vec3crd& p) { return p(0); }
inline coord_t y(const Vec3crd& p) { return p(1); }
inline coord_t z(const Vec3crd& p) { return p(2); }
/// Intermediate struct for a 3D mesh
struct Contour3D {
Pointf3s points;
std::vector<Vec3i> indices;
void merge(const Contour3D& ctr) {
Contour3D& merge(const Contour3D& ctr)
{
auto s3 = coord_t(points.size());
auto s = indices.size();
@ -44,21 +28,27 @@ struct Contour3D {
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
for(size_t n = s; n < indices.size(); n++) {
auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3;
auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3;
}
return *this;
}
void merge(const Pointf3s& triangles) {
Contour3D& merge(const Pointf3s& triangles)
{
const size_t offs = points.size();
points.insert(points.end(), triangles.begin(), triangles.end());
indices.reserve(indices.size() + points.size() / 3);
for(int i = (int)offs; i < (int)points.size(); i += 3)
for(int i = int(offs); i < int(points.size()); i += 3)
indices.emplace_back(i, i + 1, i + 2);
return *this;
}
// Write the index triangle structure to OBJ file for debugging purposes.
void to_obj(std::ostream& stream) {
void to_obj(std::ostream& stream)
{
for(auto& p : points) {
stream << "v " << p.transpose() << "\n";
}
@ -72,6 +62,31 @@ struct Contour3D {
using ClusterEl = std::vector<unsigned>;
using ClusteredPoints = std::vector<ClusterEl>;
// Clustering a set of points by the given distance.
ClusteredPoints cluster(const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
double dist,
unsigned max_points);
ClusteredPoints cluster(const PointSet& points,
double dist,
unsigned max_points);
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
unsigned max_points);
// Calculate the normals for the selected points (from 'points' set) on the
// mesh. This will call squared distance for each point.
PointSet normals(const PointSet& points,
const EigenMesh3D& mesh,
double eps = 0.05, // min distance from edges
std::function<void()> throw_on_cancel = [](){},
const std::vector<unsigned>& selected_points = {});
/// Mesh from an existing contour.
inline TriangleMesh mesh(const Contour3D& ctour) {
return {ctour.points, ctour.indices};

View File

@ -1,8 +1,9 @@
#ifndef SLACOMMON_HPP
#define SLACOMMON_HPP
#include <Eigen/Geometry>
#include <memory>
#include <vector>
#include <Eigen/Geometry>
// #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
/// alternative (raw) input format for the SLASupportTree
class EigenMesh3D {
@ -175,6 +178,8 @@ public:
}
};
using PointSet = Eigen::MatrixXd;
} // namespace sla
} // namespace Slic3r

View 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

View 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

View 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

View File

@ -5,6 +5,7 @@
#include "SLARaster.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
// For rasterizing
@ -32,25 +33,30 @@ inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.H
namespace sla {
const Raster::TMirroring Raster::NoMirror = {false, false};
const Raster::TMirroring Raster::MirrorX = {true, false};
const Raster::TMirroring Raster::MirrorY = {false, true};
const Raster::TMirroring Raster::MirrorXY = {true, true};
using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
using TRawRenderer = agg::renderer_base<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 {
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 ColorBlack;
using Format = Raster::Format;
using Format = Raster::RawData;
private:
Raster::Resolution m_resolution;
// Raster::PixelDim m_pxdim;
Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons
TBuffer m_buf;
TRawBuffer m_rbuf;
@ -59,73 +65,48 @@ private:
TRendererAA m_renderer;
std::function<double(double)> m_gammafn;
std::array<bool, 2> m_mirror;
Format m_fmt = Format::PNG;
Trafo m_trafo;
inline void flipy(agg::path_storage& path) const {
path.flip_y(0, m_resolution.height_px);
path.flip_y(0, double(m_resolution.height_px));
}
inline void flipx(agg::path_storage& path) const {
path.flip_x(0, m_resolution.width_px);
path.flip_x(0, double(m_resolution.width_px));
}
public:
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
const std::array<bool, 2>& mirror, double gamma = 1.0):
m_resolution(res),
// m_pxdim(pd),
m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm),
m_buf(res.pixels()),
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
res.width_px, res.height_px,
int(res.width_px*TPixelRenderer::num_components)),
m_pixfmt(m_rbuf),
m_raw_renderer(m_pixfmt),
m_renderer(m_raw_renderer),
m_mirror(mirror)
inline Impl(const Raster::Resolution & res,
const Raster::PixelDim & pd,
const Trafo &trafo)
: m_resolution(res)
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
, m_buf(res.pixels())
, m_rbuf(reinterpret_cast<TPixelRenderer::value_type *>(m_buf.data()),
unsigned(res.width_px),
unsigned(res.height_px),
int(res.width_px * TPixelRenderer::num_components))
, m_pixfmt(m_rbuf)
, m_raw_renderer(m_pixfmt)
, m_renderer(m_raw_renderer)
, m_trafo(trafo)
{
m_renderer.color(ColorWhite);
if(gamma > 0) m_gammafn = agg::gamma_power(gamma);
if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma);
else m_gammafn = agg::gamma_threshold(0.5);
clear();
}
inline Impl(const Raster::Resolution& res,
const Raster::PixelDim &pd,
Format fmt,
double gamma = 1.0):
Impl(res, pd, {false, false}, gamma)
{
switch (fmt) {
case Format::PNG: m_mirror = {false, true}; break;
case Format::RAW: m_mirror = {false, false}; break;
}
m_fmt = fmt;
}
template<class P> void draw(const P &poly) {
agg::rasterizer_scanline_aa<> ras;
agg::scanline_p8 scanlines;
ras.gamma(m_gammafn);
auto&& path = to_path(contour(poly));
if(m_mirror[X]) flipx(path);
if(m_mirror[Y]) flipy(path);
ras.add_path(path);
for(auto& h : holes(poly)) {
auto&& holepath = to_path(h);
if(m_mirror[X]) flipx(holepath);
if(m_mirror[Y]) flipy(holepath);
ras.add_path(holepath);
}
ras.add_path(to_path(contour(poly)));
for(auto& h : holes(poly)) ras.add_path(to_path(h));
agg::render_scanlines(ras, scanlines, m_renderer);
}
@ -135,10 +116,15 @@ public:
}
inline TBuffer& buffer() { return m_buf; }
inline const TBuffer& buffer() const { return m_buf; }
inline Format format() const { return m_fmt; }
inline const Raster::Resolution resolution() { return m_resolution; }
inline const Raster::PixelDim pixdim()
{
return {SCALING_FACTOR / m_pxdim_scaled.w_mm,
SCALING_FACTOR / m_pxdim_scaled.h_mm};
}
private:
inline double getPx(const Point& p) {
@ -162,49 +148,67 @@ private:
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;
auto it = poly.begin();
auto it = v.begin();
path.move_to(getPx(*it), getPy(*it));
while(++it != v.end()) path.line_to(getPx(*it), getPy(*it));
path.line_to(getPx(v.front()), getPy(v.front()));
while(++it != poly.end())
path.line_to(getPx(*it), getPy(*it));
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);
path.line_to(getPx(poly.front()), getPy(poly.front()));
return path;
}
};
const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
const TPixel Raster::Impl::ColorWhite = TPixel(255);
const TPixel Raster::Impl::ColorBlack = TPixel(0);
Raster::Raster() { reset(); }
Raster::Raster(const Raster::Resolution &r,
const Raster::PixelDim & pd,
const Raster::Trafo & tr)
{
reset(r, pd, tr);
}
template<> Raster::Raster() { reset(); };
Raster::~Raster() = default;
// Raster::Raster(Raster &&m) = default;
// Raster& Raster::operator=(Raster&&) = default;
// FIXME: remove after migrating to higher version of windows compiler
Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {}
Raster& Raster::operator=(Raster &&m) {
m_impl = std::move(m.m_impl); return *this;
}
Raster::Raster(Raster &&m) = default;
Raster &Raster::operator=(Raster &&) = default;
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
Format fmt, double gamma)
const Trafo &trafo)
{
m_impl.reset();
m_impl.reset(new Impl(r, pd, fmt, gamma));
}
void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
const std::array<bool, 2>& mirror, double gamma)
{
m_impl.reset();
m_impl.reset(new Impl(r, pd, mirror, gamma));
m_impl.reset(new Impl(r, pd, trafo));
}
void Raster::reset()
@ -214,9 +218,16 @@ void Raster::reset()
Raster::Resolution Raster::resolution() const
{
if(m_impl) return m_impl->resolution();
if (m_impl) return m_impl->resolution();
return Resolution(0, 0);
return Resolution{0, 0};
}
Raster::PixelDim Raster::pixel_dimensions() const
{
if (m_impl) return m_impl->pixdim();
return PixelDim{0., 0.};
}
void Raster::clear()
@ -227,103 +238,83 @@ void Raster::clear()
void Raster::draw(const ExPolygon &expoly)
{
assert(m_impl);
m_impl->draw(expoly);
}
void Raster::draw(const ClipperLib::Polygon &poly)
{
assert(m_impl);
m_impl->draw(poly);
}
void Raster::save(std::ostream& stream, Format fmt)
uint8_t Raster::read_pixel(size_t x, size_t y) const
{
assert(m_impl);
if(!stream.good()) return;
switch(fmt) {
case Format::PNG: {
auto& b = m_impl->buffer();
size_t out_len = 0;
void * rawdata = tdefl_write_image_to_png_file_in_memory(
b.data(),
int(resolution().width_px),
int(resolution().height_px), 1, &out_len);
if(rawdata == nullptr) break;
stream.write(static_cast<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));
}
}
assert (m_impl);
TPixel::value_type px;
m_impl->buffer()[y * resolution().width_px + x].get(px);
return px;
}
void Raster::save(std::ostream &stream)
PNGImage & PNGImage::serialize(const Raster &raster)
{
save(stream, m_impl->format());
size_t s = 0;
m_buffer.clear();
void *rawdata = tdefl_write_image_to_png_file_in_memory(
get_internals(raster).buffer().data(),
int(raster.resolution().width_px),
int(raster.resolution().height_px), 1, &s);
// On error, data() will return an empty vector. No other info can be
// retrieved from miniz anyway...
if (rawdata == nullptr) return *this;
auto ptr = static_cast<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;
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)};
return stream;
}
RawBytes Raster::save()
Raster::RawData::~RawData() = default;
PPMImage & PPMImage::serialize(const Raster &raster)
{
return save(m_impl->format());
auto header = std::string("P5 ") +
std::to_string(raster.resolution().width_px) + " " +
std::to_string(raster.resolution().height_px) + " " + "255 ";
const auto &impl = get_internals(raster);
auto sz = impl.buffer().size() * sizeof(TBuffer::value_type);
size_t s = sz + header.size();
m_buffer.clear();
m_buffer.reserve(s);
auto buff = reinterpret_cast<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

View File

@ -8,45 +8,13 @@
#include <utility>
#include <cstdint>
#include <libslic3r/ExPolygon.hpp>
namespace ClipperLib { struct Polygon; }
namespace Slic3r {
class ExPolygon;
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
* 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;
public:
/// Supported compression types
enum class Format {
RAW, //!> Uncompressed pixel data
PNG //!> PNG compression
// Raw byte buffer paired with its size. Suitable for compressed image data.
class RawData
{
protected:
std::vector<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.
@ -86,10 +72,35 @@ public:
w_mm(px_width_mm), h_mm(px_height_mm) {}
};
/// Constructor taking the resolution and the pixel dimension.
template <class...Args> Raster(Args...args) {
reset(std::forward<Args>(args)...);
}
enum Orientation { roLandscape, roPortrait };
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& operator=(const Raster& cpy) = delete;
@ -98,17 +109,9 @@ public:
~Raster();
/// Reallocated everything for the given resolution and pixel dimension.
/// The third parameter is either the X, Y mirroring or a supported format
/// for which the correct mirroring will be configured.
void reset(const Resolution&,
const PixelDim&,
const std::array<bool, 2>& mirror,
double gamma = 1.0);
void reset(const Resolution& r,
const PixelDim& pd,
Format o,
double gamma = 1.0);
const Trafo &tr = {});
/**
* Release the allocated resources. Drawing in this state ends in
@ -118,6 +121,7 @@ public:
/// Get the resolution of the raster.
Resolution resolution() const;
PixelDim pixel_dimensions() const;
/// Clear the raster with black color.
void clear();
@ -126,24 +130,28 @@ public:
void draw(const ExPolygon& poly);
void draw(const ClipperLib::Polygon& poly);
// Saving the raster:
// It is possible to override the format given in the constructor but
// be aware that the mirroring will not be modified.
uint8_t read_pixel(size_t w, size_t h) const;
/// Save the raster on the specified stream.
void save(std::ostream& stream, Format);
void save(std::ostream& stream);
inline bool empty() const { return ! bool(m_impl); }
/// Save into a continuous byte stream which is returned.
RawBytes save(Format fmt);
RawBytes save();
};
// This prevents the duplicate default constructor warning on MSVC2013
template<> Raster::Raster();
class PNGImage: public Raster::RawData {
public:
PNGImage& serialize(const Raster &raster) override;
std::string get_file_extension() const override { return "png"; }
};
class PPMImage: public Raster::RawData {
public:
PPMImage& serialize(const Raster &raster) override;
std::string get_file_extension() const override { return "ppm"; }
};
std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes);
} // sla
} // Slic3r
#endif // SLARASTER_HPP

View File

@ -10,7 +10,7 @@
namespace Slic3r { namespace sla {
std::string SLARasterWriter::createIniContent(const std::string& projectname) const
std::string RasterWriter::createIniContent(const std::string& projectname) const
{
std::string out("action = print\njobDir = ");
out += projectname + "\n";
@ -21,39 +21,14 @@ std::string SLARasterWriter::createIniContent(const std::string& projectname) co
return out;
}
void SLARasterWriter::flpXY(ClipperLib::Polygon &poly)
{
for(auto& p : poly.Contour) std::swap(p.X, p.Y);
std::reverse(poly.Contour.begin(), poly.Contour.end());
RasterWriter::RasterWriter(const Raster::Resolution &res,
const Raster::PixelDim & pixdim,
const Raster::Trafo & trafo,
double gamma)
: m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma)
{}
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)
{
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)
void RasterWriter::save(const std::string &fpath, const std::string &prjname)
{
try {
Zipper zipper(fpath); // zipper with no compression
@ -103,7 +78,7 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
} // namespace
void SLARasterWriter::set_config(const DynamicPrintConfig &cfg)
void RasterWriter::set_config(const DynamicPrintConfig &cfg)
{
m_config["layerHeight"] = get_cfg_value(cfg, "layer_height");
m_config["expTime"] = get_cfg_value(cfg, "exposure_time");
@ -114,11 +89,11 @@ void SLARasterWriter::set_config(const DynamicPrintConfig &cfg)
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
m_config["fileCreationTimestamp"] = Utils::current_utc_time2str();
m_config["fileCreationTimestamp"] = Utils::utc_timestamp();
m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
}
void SLARasterWriter::set_statistics(const PrintStatistics &stats)
void RasterWriter::set_statistics(const PrintStatistics &stats)
{
m_config["usedMaterial"] = std::to_string(stats.used_material);
m_config["numFade"] = std::to_string(stats.num_fade);

View File

@ -15,20 +15,17 @@
namespace Slic3r { namespace sla {
// Implementation for PNG raster output
// API to write the zipped sla output layers and metadata.
// Implementation uses PNG raster output.
// Be aware that if a large number of layers are allocated, it can very well
// exhaust the available memory especially on 32 bit platform.
// This class is designed to be used in parallel mode. Layers have an ID and
// each layer can be written and compressed independently (in parallel).
// At the end when all layers where written, the save method can be used to
// write out the result into a zipped archive.
class SLARasterWriter
class RasterWriter
{
public:
enum Orientation {
roLandscape,
roPortrait
};
// Used for addressing parameters of set_statistics()
struct PrintStatistics
@ -45,7 +42,7 @@ private:
// A struct to bind the raster image data and its compressed bytes together.
struct Layer {
Raster raster;
RawBytes rawbytes;
PNGImage rawbytes;
Layer() = default;
@ -61,70 +58,55 @@ private:
// parallel. Later we can write every layer to the disk sequentially.
std::vector<Layer> m_layers_rst;
Raster::Resolution m_res;
Raster::PixelDim m_pxdim;
std::array<bool, 2> m_mirror;
double m_gamma;
Raster::PixelDim m_pxdim;
Raster::Trafo m_trafo;
double m_gamma;
std::map<std::string, std::string> m_config;
std::string createIniContent(const std::string& projectname) const;
static void flpXY(ClipperLib::Polygon& poly);
static void flpXY(ExPolygon& poly);
public:
SLARasterWriter(const Raster::Resolution &res,
const Raster::PixelDim &pixdim,
const std::array<bool, 2> &mirror,
double gamma = 1.);
SLARasterWriter(const SLARasterWriter& ) = delete;
SLARasterWriter& operator=(const SLARasterWriter&) = delete;
SLARasterWriter(SLARasterWriter&& m) = default;
SLARasterWriter& operator=(SLARasterWriter&&) = default;
// SLARasterWriter is using Raster in custom mirroring mode
RasterWriter(const Raster::Resolution &res,
const Raster::PixelDim & pixdim,
const Raster::Trafo & trafo,
double gamma = 1.);
RasterWriter(const RasterWriter& ) = delete;
RasterWriter& operator=(const RasterWriter&) = delete;
RasterWriter(RasterWriter&& m) = default;
RasterWriter& operator=(RasterWriter&&) = default;
inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); }
inline unsigned layers() const { return unsigned(m_layers_rst.size()); }
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr,
Orientation o = roPortrait)
template<class Poly> void draw_polygon(const Poly& p, unsigned lyr)
{
assert(lyr < m_layers_rst.size());
switch (o) {
case roPortrait: {
Poly poly(p);
flpXY(poly);
m_layers_rst[lyr].raster.draw(poly);
break;
}
case roLandscape:
m_layers_rst[lyr].raster.draw(p);
break;
}
m_layers_rst[lyr].raster.draw(p);
}
inline void begin_layer(unsigned lyr) {
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo);
}
inline void begin_layer() {
m_layers_rst.emplace_back();
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma);
m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo);
}
inline void finish_layer(unsigned lyr_id) {
assert(lyr_id < m_layers_rst.size());
m_layers_rst[lyr_id].rawbytes =
m_layers_rst[lyr_id].raster.save(Raster::Format::PNG);
m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster);
m_layers_rst[lyr_id].raster.reset();
}
inline void finish_layer() {
if(!m_layers_rst.empty()) {
m_layers_rst.back().rawbytes =
m_layers_rst.back().raster.save(Raster::Format::PNG);
m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster);
m_layers_rst.back().raster.reset();
}
}

View File

@ -39,14 +39,19 @@ public:
insert(std::make_pair(v, unsigned(idx)));
}
std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>);
std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k);
std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>) const;
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
size_t size() const;
bool empty() const { return size() == 0; }
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>;

File diff suppressed because it is too large Load Diff

View File

@ -2,24 +2,14 @@
#define SLASUPPORTTREE_HPP
#include <vector>
#include <array>
#include <cstdint>
#include <memory>
#include <Eigen/Geometry>
#include "SLACommon.hpp"
#include "SLAPad.hpp"
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 Model;
class ModelInstance;
@ -32,13 +22,17 @@ using ExPolygons = std::vector<ExPolygon>;
namespace sla {
enum class PillarConnectionMode {
enum class PillarConnectionMode
{
zigzag,
cross,
dynamic
};
struct SupportConfig {
struct SupportConfig
{
bool enabled = true;
// Radius in mm of the pointing side of the head.
double head_front_radius_mm = 0.2;
@ -86,6 +80,11 @@ struct SupportConfig {
// body. This is only useful when elevation is set to zero.
double pillar_base_safety_distance_mm = 0.5;
double head_fullwidth() const {
return 2 * head_front_radius_mm + head_width_mm +
2 * head_back_radius_mm - head_penetration_mm;
}
// /////////////////////////////////////////////////////////////////////////
// Compile time configuration values (candidates for runtime)
// /////////////////////////////////////////////////////////////////////////
@ -104,101 +103,78 @@ struct SupportConfig {
static const unsigned max_bridges_on_pillar;
};
struct PoolConfig;
enum class MeshType { Support, Pad };
/// A Control structure for the support calculation. Consists of the status
/// indicator callback and the stop condition predicate.
struct Controller {
struct JobController
{
using StatusFn = std::function<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
std::function<void(unsigned, const std::string&)> statuscb =
[](unsigned, const std::string&){};
StatusFn statuscb = [](unsigned, const std::string&){};
// 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
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
// consider it a hard abort. stopcondition is permits the algorithm to
// 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,
const SupportConfig &c)
: emesh{trmsh}, pts{sp}, cfg{c}
{}
// needed for find best rotation
//EigenMesh3D to_eigenmesh(const ModelObject& model);
// Simple conversion of 'vector of points' to an Eigen matrix
//PointSet to_point_set(const std::vector<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.
class SLASupportTree {
class Impl; // persistent support data
std::unique_ptr<Impl> m_impl;
Impl& get() { return *m_impl; }
const Impl& get() const { return *m_impl; }
friend void add_sla_supports(Model&,
const SupportConfig&,
const Controller&);
// The generation algorithm is quite long and will be captured in a separate
// class with private data, helper methods, etc... This data is only needed
// during the calculation whereas the Impl class contains the persistent
// data, mostly the meshes.
class Algorithm;
// Generate the 3D supports for a model intended for SLA print. This
// will instantiate the Algorithm class and call its appropriate methods
// with status indication.
bool generate(const std::vector<SupportPoint>& pts,
const EigenMesh3D& mesh,
const SupportConfig& cfg = {},
const Controller& ctl = {});
class SupportTree
{
JobController m_ctl;
public:
using UPtr = std::unique_ptr<SupportTree>;
SLASupportTree(double ground_level = 0.0);
static UPtr create(const SupportableMesh &input,
const JobController &ctl = {});
SLASupportTree(const std::vector<SupportPoint>& pts,
const EigenMesh3D& em,
const SupportConfig& cfg = {},
const Controller& ctl = {});
virtual ~SupportTree() = default;
SLASupportTree(const SLASupportTree&) = delete;
SLASupportTree& operator=(const SLASupportTree&) = delete;
virtual const TriangleMesh &retrieve_mesh(MeshType meshtype) const = 0;
~SLASupportTree();
/// Adding the "pad" under the supports.
/// modelbase will be used according to the embed_object flag in PoolConfig.
/// If set, the plate will be interpreted as the model's intrinsic pad.
/// Otherwise, the modelbase will be unified with the base plate calculated
/// from the supports.
virtual const TriangleMesh &add_pad(const ExPolygons &modelbase,
const PadConfig & pcfg) = 0;
/// Get the whole mesh united into the output TriangleMesh
/// WITHOUT THE PAD
const TriangleMesh& merged_mesh() const;
void merged_mesh_with_pad(TriangleMesh&) const;
virtual void remove_pad() = 0;
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.
/// If set, the plate will interpreted as the model's intrinsic pad.
/// Otherwise, the modelbase will be unified with the base plate calculated
/// from the supports.
const TriangleMesh& add_pad(const ExPolygons& modelbase,
const PoolConfig& pcfg) const;
/// Get the pad geometry
const TriangleMesh& get_pad() const;
void remove_pad();
void retrieve_full_mesh(TriangleMesh &outmesh) const;
const JobController &ctl() const { return m_ctl; }
};
}

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

View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@ -77,7 +77,7 @@ bool PointIndex::remove(const PointIndexEl& el)
}
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;
@ -86,7 +86,7 @@ PointIndex::query(std::function<bool(const PointIndexEl &)> fn)
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;
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);
}
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
{
for(const auto &el : m_impl->m_store) fn(el);
}
/* **************************************************************************
* BoxIndex implementation
* ************************************************************************** */
@ -274,6 +279,8 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
* Misc functions
* ****************************************************************************/
namespace {
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05)
{
@ -289,11 +296,13 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
return std::sqrt(p.transpose() * p);
}
}
PointSet normals(const PointSet& points,
const EigenMesh3D& mesh,
double eps,
std::function<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)
return {};
@ -419,9 +428,17 @@ PointSet normals(const PointSet& points,
return ret;
}
namespace bgi = boost::geometry::index;
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
namespace {
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
{
return e1.second < e2.second;
};
ClusteredPoints cluster(Index3D &sindex,
unsigned max_points,
std::function<std::vector<PointIndexEl>(
@ -436,22 +453,19 @@ ClusteredPoints cluster(Index3D &sindex,
{
for(auto& p : pts) {
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);
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
Elems newpts;
std::set_difference(tmp.begin(), tmp.end(),
cluster.begin(), cluster.end(),
std::back_inserter(newpts), cmp);
std::back_inserter(newpts), cmp_ptidx_elements);
int c = max_points && newpts.size() + cluster.size() > max_points?
int(max_points - cluster.size()) : int(newpts.size());
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
std::sort(cluster.begin(), cluster.end(), cmp);
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
group(newpts, cluster);
@ -479,7 +493,6 @@ ClusteredPoints cluster(Index3D &sindex,
return result;
}
namespace {
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
const PointIndexEl& p,
double dist,
@ -496,7 +509,8 @@ std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
return tmp;
}
}
} // namespace
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
@ -558,5 +572,5 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
});
}
}
}
} // namespace sla
} // namespace Slic3r

View File

@ -1,6 +1,6 @@
#include "SLAPrint.hpp"
#include "SLA/SLASupportTree.hpp"
#include "SLA/SLABasePool.hpp"
#include "SLA/SLAPad.hpp"
#include "SLA/SLAAutoSupports.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
@ -32,17 +32,19 @@
namespace Slic3r {
using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
class SLAPrintObject::SupportData
class SLAPrintObject::SupportData : public sla::SupportableMesh
{
public:
sla::EigenMesh3D emesh; // index-triangle representation
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
SupportTreePtr support_tree_ptr; // the supports
sla::SupportTree::UPtr support_tree_ptr; // the 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 {
@ -53,7 +55,7 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS =
30, // slaposObjectSlice,
20, // slaposSupportPoints,
10, // slaposSupportTree,
10, // slaposBasePool,
10, // slaposPad,
30, // slaposSliceSupports,
};
@ -64,7 +66,7 @@ std::string OBJ_STEP_LABELS(size_t idx)
case slaposObjectSlice: return L("Slicing model");
case slaposSupportPoints: return L("Generating support points");
case slaposSupportTree: return L("Generating support tree");
case slaposBasePool: return L("Generating pad");
case slaposPad: return L("Generating pad");
case slaposSliceSupports: return L("Slicing supports");
default:;
}
@ -584,6 +586,7 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) {
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
sla::SupportConfig scfg;
scfg.enabled = c.supports_enable.getBool();
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
@ -612,12 +615,13 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
return scfg;
}
sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
sla::PoolConfig::EmbedObject ret;
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
sla::PadConfig::EmbedObject ret;
ret.enabled = is_zero_elevation(c);
if(ret.enabled) {
ret.everywhere = c.pad_around_object_everywhere.getBool();
ret.object_gap_mm = c.pad_object_gap.getFloat();
ret.stick_width_mm = c.pad_object_connector_width.getFloat();
ret.stick_stride_mm = c.pad_object_connector_stride.getFloat();
@ -628,17 +632,15 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
return ret;
}
sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
sla::PoolConfig pcfg;
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) {
sla::PadConfig pcfg;
pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat();
pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat();
pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0;
// We do not support radius for now
pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat();
pcfg.min_wall_height_mm = c.pad_wall_height.getFloat();
pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat();
pcfg.wall_height_mm = c.pad_wall_height.getFloat();
pcfg.brim_size_mm = c.pad_brim_size.getFloat();
// set builtin pad implicitly ON
pcfg.embed_object = builtin_pad_cfg(c);
@ -646,6 +648,13 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) {
return pcfg;
}
bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg)
{
// An empty pad can only be created if embed_object mode is enabled
// and the pad is not forced everywhere
return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere);
}
}
std::string SLAPrint::validate() const
@ -663,17 +672,12 @@ std::string SLAPrint::validate() const
sla::SupportConfig cfg = make_support_cfg(po->config());
double pinhead_width =
2 * cfg.head_front_radius_mm +
cfg.head_width_mm +
2 * cfg.head_back_radius_mm -
cfg.head_penetration_mm;
double elv = cfg.object_elevation_mm;
sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config());
sla::PadConfig padcfg = make_pad_cfg(po->config());
sla::PadConfig::EmbedObject &builtinpad = padcfg.embed_object;
if(supports_en && !builtinpad.enabled && elv < pinhead_width )
if(supports_en && !builtinpad.enabled && elv < cfg.head_fullwidth())
return L(
"Elevation is too low for object. Use the \"Pad around "
"object\" feature to print the object without elevation.");
@ -686,6 +690,9 @@ std::string SLAPrint::validate() const
"distance' has to be greater than the 'Pad object gap' "
"parameter to avoid this.");
}
std::string pval = padcfg.validate();
if (!pval.empty()) return pval;
}
double expt_max = m_printer_config.max_exposure_time.getFloat();
@ -876,8 +883,7 @@ void SLAPrint::process()
// Construction of this object does the calculation.
this->throw_if_canceled();
SLAAutoSupports auto_supports(po.transformed_mesh(),
po.m_supportdata->emesh,
SLAAutoSupports auto_supports(po.m_supportdata->emesh,
po.get_model_slices(),
heights,
config,
@ -887,10 +893,10 @@ void SLAPrint::process()
// Now let's extract the result.
const std::vector<sla::SupportPoint>& points = auto_supports.output();
this->throw_if_canceled();
po.m_supportdata->support_points = points;
po.m_supportdata->pts = points;
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
<< po.m_supportdata->support_points.size();
<< po.m_supportdata->pts.size();
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
// the update status to GLGizmoSlaSupports
@ -902,29 +908,19 @@ void SLAPrint::process()
else {
// There are either some points on the front-end, or the user
// removed them on purpose. No calculation will be done.
po.m_supportdata->support_points = po.transformed_support_points();
po.m_supportdata->pts = po.transformed_support_points();
}
// If the zero elevation mode is engaged, we have to filter out all the
// points that are on the bottom of the object
if (is_zero_elevation(po.config())) {
double gnd = po.m_supportdata->emesh.ground_level();
auto & pts = po.m_supportdata->support_points;
double tolerance = po.config().pad_enable.getBool()
? po.m_config.pad_wall_thickness.getFloat()
: po.m_config.support_base_height.getFloat();
// get iterator to the reorganized vector end
auto endit = std::remove_if(
pts.begin(),
pts.end(),
[tolerance, gnd](const sla::SupportPoint &sp) {
double diff = std::abs(gnd - double(sp.pos(Z)));
return diff <= tolerance;
});
// erase all elements after the new end
pts.erase(endit, pts.end());
remove_bottom_points(po.m_supportdata->pts,
po.m_supportdata->emesh.ground_level(),
tolerance);
}
};
@ -933,45 +929,31 @@ void SLAPrint::process()
{
if(!po.m_supportdata) return;
sla::PoolConfig pcfg = make_pool_config(po.m_config);
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
if (pcfg.embed_object)
po.m_supportdata->emesh.ground_level_offset(
pcfg.min_wall_thickness_mm);
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
if(!po.m_config.supports_enable.getBool()) {
// Generate empty support tree. It can still host a pad
po.m_supportdata->support_tree_ptr.reset(
new SLASupportTree(po.m_supportdata->emesh.ground_level()));
return;
}
sla::SupportConfig scfg = make_support_cfg(po.m_config);
sla::Controller ctl;
po.m_supportdata->cfg = make_support_cfg(po.m_config);
// scaling for the sub operations
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
double init = m_report_status.status();
JobController ctl;
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg)
{
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
double current = init + st * d;
if(std::round(m_report_status.status()) < std::round(current))
if (std::round(m_report_status.status()) < std::round(current))
m_report_status(*this, current,
OBJ_STEP_LABELS(slaposSupportTree),
SlicingStatus::DEFAULT,
logmsg);
SlicingStatus::DEFAULT, logmsg);
};
ctl.stopcondition = [this](){ return canceled(); };
ctl.stopcondition = [this]() { return canceled(); };
ctl.cancelfn = [this]() { throw_if_canceled(); };
po.m_supportdata->support_tree_ptr.reset(
new SLASupportTree(po.m_supportdata->support_points,
po.m_supportdata->emesh, scfg, ctl));
po.m_supportdata->create_support_tree(ctl);
if (!po.m_config.supports_enable.getBool()) return;
throw_if_canceled();
@ -980,10 +962,9 @@ void SLAPrint::process()
// This is to prevent "Done." being displayed during merged_mesh()
m_report_status(*this, -1, L("Visualizing supports"));
po.m_supportdata->support_tree_ptr->merged_mesh();
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
<< po.m_supportdata->support_points.size();
<< po.m_supportdata->pts.size();
// Check the mesh for later troubleshooting.
if(po.support_mesh().empty())
@ -993,7 +974,7 @@ void SLAPrint::process()
};
// This step generates the sla base pad
auto base_pool = [this](SLAPrintObject& po) {
auto generate_pad = [this](SLAPrintObject& po) {
// this step can only go after the support tree has been created
// and before the supports had been sliced. (or the slicing has to be
// repeated)
@ -1001,10 +982,10 @@ void SLAPrint::process()
if(po.m_config.pad_enable.getBool())
{
// Get the distilled pad configuration from the config
sla::PoolConfig pcfg = make_pool_config(po.m_config);
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
ExPolygons bp; // This will store the base plate of the pad.
double pad_h = sla::get_pad_fullheight(pcfg);
double pad_h = pcfg.full_height();
const TriangleMesh &trmesh = po.transformed_mesh();
// This call can get pretty time consuming
@ -1015,15 +996,19 @@ void SLAPrint::process()
// we sometimes call it "builtin pad" is enabled so we will
// get a sample from the bottom of the mesh and use it for pad
// creation.
sla::base_plate(trmesh,
bp,
float(pad_h),
float(po.m_config.layer_height.getFloat()),
thrfn);
sla::pad_blueprint(trmesh, bp, float(pad_h),
float(po.m_config.layer_height.getFloat()),
thrfn);
}
pcfg.throw_on_cancel = thrfn;
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(MeshType::Pad);
if (!validate_pad(pad_mesh, pcfg))
throw std::runtime_error(
L("No pad can be generated for this model with the "
"current configuration"));
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
po.m_supportdata->support_tree_ptr->remove_pad();
}
@ -1191,9 +1176,8 @@ void SLAPrint::process()
{
ClipperPolygon poly;
// We need to reverse if flpXY OR is_lefthanded is true but
// not if both are true which is a logical inequality (XOR)
bool needreverse = /*flpXY !=*/ is_lefthanded;
// We need to reverse if is_lefthanded is true but
bool needreverse = is_lefthanded;
// should be a move
poly.Contour.reserve(polygon.contour.size() + 1);
@ -1396,7 +1380,7 @@ void SLAPrint::process()
if(canceled()) return;
// Set up the printer, allocate space for all the layers
sla::SLARasterWriter &printer = init_printer();
sla::RasterWriter &printer = init_printer();
auto lvlcnt = unsigned(m_printer_input.size());
printer.layers(lvlcnt);
@ -1416,11 +1400,9 @@ void SLAPrint::process()
SpinMutex slck;
auto orientation = get_printer_orientation();
// procedure to process one height level. This will run in parallel
auto lvlfn =
[this, &slck, &printer, increment, &dstatus, &pst, orientation]
[this, &slck, &printer, increment, &dstatus, &pst]
(unsigned level_id)
{
if(canceled()) return;
@ -1431,7 +1413,7 @@ void SLAPrint::process()
printer.begin_layer(level_id);
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
printer.draw_polygon(poly, level_id, orientation);
printer.draw_polygon(poly, level_id);
// Finish the layer for later saving it.
printer.finish_layer(level_id);
@ -1459,7 +1441,7 @@ void SLAPrint::process()
tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
// Set statistics values to the printer
sla::SLARasterWriter::PrintStatistics stats;
sla::RasterWriter::PrintStatistics stats;
stats.used_material = (m_print_statistics.objects_used_material +
m_print_statistics.support_used_material) /
1000;
@ -1478,12 +1460,12 @@ void SLAPrint::process()
slaposFn pobj_program[] =
{
slice_model, support_points, support_tree, base_pool, slice_supports
slice_model, support_points, support_tree, generate_pad, slice_supports
};
// We want to first process all objects...
std::vector<SLAPrintObjectStep> level1_obj_steps = {
slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool
slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
};
// 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;
}
sla::SLARasterWriter & SLAPrint::init_printer()
sla::RasterWriter & SLAPrint::init_printer()
{
sla::Raster::Resolution res;
sla::Raster::PixelDim pxdim;
std::array<bool, 2> mirror;
double gamma;
double w = m_printer_config.display_width.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[Y] = m_printer_config.display_mirror_y.getBool();
if (get_printer_orientation() == sla::SLARasterWriter::roPortrait) {
auto orientation = get_printer_orientation();
if (orientation == sla::Raster::roPortrait) {
std::swap(w, h);
std::swap(pw, ph);
// XY flipping implicitly does an X mirror
mirror[X] = !mirror[X];
}
res = sla::Raster::Resolution{pw, ph};
pxdim = sla::Raster::PixelDim{w / pw, h / ph};
sla::Raster::Trafo tr{orientation, mirror};
tr.gamma = m_printer_config.gamma_correction.getFloat();
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);
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 == "support_object_elevation"
|| opt_key == "pad_around_object"
|| opt_key == "pad_around_object_everywhere"
|| opt_key == "slice_closing_radius") {
steps.emplace_back(slaposObjectSlice);
} else if (
@ -1754,6 +1734,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
steps.emplace_back(slaposSupportTree);
} else if (
opt_key == "pad_wall_height"
|| opt_key == "pad_brim_size"
|| opt_key == "pad_max_merge_distance"
|| opt_key == "pad_wall_slope"
|| 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_penetration"
) {
steps.emplace_back(slaposBasePool);
steps.emplace_back(slaposPad);
} else {
// All keys should be covered.
assert(false);
@ -1782,12 +1763,12 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
if (step == slaposObjectSlice) {
invalidated |= this->invalidate_all_steps();
} else if (step == slaposSupportPoints) {
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports });
invalidated |= this->invalidate_steps({ slaposSupportTree, slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposSupportTree) {
invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports });
invalidated |= this->invalidate_steps({ slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposBasePool) {
} else if (step == slaposPad) {
invalidated |= this->invalidate_steps({slaposSliceSupports});
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposSliceSupports) {
@ -1813,8 +1794,8 @@ double SLAPrintObject::get_elevation() const {
// its walls but currently it is half of its thickness. Whatever it
// will be in the future, we provide the config to the get_pad_elevation
// method and we will have the correct value
sla::PoolConfig pcfg = make_pool_config(m_config);
if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg);
sla::PadConfig pcfg = make_pad_cfg(m_config);
if(!pcfg.embed_object) ret += pcfg.required_elevation();
}
return ret;
@ -1825,7 +1806,7 @@ double SLAPrintObject::get_current_elevation() const
if (is_zero_elevation(m_config)) return 0.;
bool has_supports = is_step_done(slaposSupportTree);
bool has_pad = is_step_done(slaposBasePool);
bool has_pad = is_step_done(slaposPad);
if(!has_supports && !has_pad)
return 0;
@ -1866,7 +1847,7 @@ const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f);
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
@ -1896,7 +1877,7 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
switch (step) {
case slaposSupportTree:
return ! this->support_mesh().empty();
case slaposBasePool:
case slaposPad:
return ! this->pad_mesh().empty();
default:
return false;
@ -1908,7 +1889,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
switch (step) {
case slaposSupportTree:
return this->support_mesh();
case slaposBasePool:
case slaposPad:
return this->pad_mesh();
default:
return TriangleMesh();
@ -1917,18 +1898,20 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
const TriangleMesh& SLAPrintObject::support_mesh() const
{
if(m_config.supports_enable.getBool() && m_supportdata &&
m_supportdata->support_tree_ptr) {
return m_supportdata->support_tree_ptr->merged_mesh();
}
sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr;
if(m_config.supports_enable.getBool() && m_supportdata && stree)
return stree->retrieve_mesh(sla::MeshType::Support);
return EMPTY_MESH;
}
const TriangleMesh& SLAPrintObject::pad_mesh() const
{
if(m_config.pad_enable.getBool() && m_supportdata && m_supportdata->support_tree_ptr)
return m_supportdata->support_tree_ptr->get_pad();
sla::SupportTree::UPtr &stree = m_supportdata->support_tree_ptr;
if(m_config.pad_enable.getBool() && m_supportdata && stree)
return stree->retrieve_mesh(sla::MeshType::Pad);
return EMPTY_MESH;
}

View File

@ -21,7 +21,7 @@ enum SLAPrintObjectStep : unsigned int {
slaposObjectSlice,
slaposSupportPoints,
slaposSupportTree,
slaposBasePool,
slaposPad,
slaposSliceSupports,
slaposCount
};
@ -54,7 +54,7 @@ public:
bool is_left_handed() const { return m_left_handed; }
struct Instance {
Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {}
Instance(ObjectID inst_id, const Point &shft, float rot) : instance_id(inst_id), shift(shft), rotation(rot) {}
bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; }
// ID of the corresponding ModelInstance.
ObjectID instance_id;
@ -440,7 +440,7 @@ private:
std::vector<PrintLayer> m_printer_input;
// The printer itself
std::unique_ptr<sla::SLARasterWriter> m_printer;
std::unique_ptr<sla::RasterWriter> m_printer;
// Estimated print time, material consumed.
SLAPrintStatistics m_print_statistics;
@ -459,14 +459,13 @@ private:
double status() const { return m_st; }
} m_report_status;
sla::SLARasterWriter &init_printer();
sla::RasterWriter &init_printer();
inline sla::SLARasterWriter::Orientation get_printer_orientation() const
inline sla::Raster::Orientation get_printer_orientation() const
{
auto ro = m_printer_config.display_orientation.getInt();
return ro == sla::SLARasterWriter::roPortrait ?
sla::SLARasterWriter::roPortrait :
sla::SLARasterWriter::roLandscape;
return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait :
sla::Raster::roLandscape;
}
friend SLAPrintObject;

View File

@ -10,12 +10,15 @@ namespace Slic3r {
class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons;
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false);
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
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<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false);
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false);
const bool constexpr NORMALS_UP = false;
const bool constexpr NORMALS_DOWN = true;
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = NORMALS_UP);
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = NORMALS_UP);
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

View File

@ -3,91 +3,174 @@
#include <iomanip>
#include <sstream>
#include <chrono>
#include <cassert>
#include <ctime>
#include <cstdio>
//#include <boost/date_time/local_time/local_time.hpp>
//#include <boost/chrono.hpp>
#ifdef _MSC_VER
#include <map>
#endif
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#endif /* WIN32 */
#include "libslic3r/Utils.hpp"
namespace Slic3r {
namespace Utils {
namespace {
// "YYYY-MM-DD at HH:MM::SS [UTC]"
// If TimeZone::utc is used with the conversion functions, it will append the
// UTC letters to the end.
static const constexpr char *const SLICER_UTC_TIME_FMT = "%Y-%m-%d at %T";
// FIXME: after we switch to gcc > 4.9 on the build server, please remove me
#if defined(__GNUC__) && __GNUC__ <= 4
std::string put_time(const std::tm *tm, const char *fmt)
// ISO8601Z representation of time, without time zone info
static const constexpr char *const ISO8601Z_TIME_FMT = "%Y%m%dT%H%M%SZ";
static const char * get_fmtstr(TimeFormat fmt)
{
static const constexpr int MAX_CHARS = 200;
char out[MAX_CHARS];
std::strftime(out, MAX_CHARS, fmt, tm);
return out;
switch (fmt) {
case TimeFormat::gcode: return SLICER_UTC_TIME_FMT;
case TimeFormat::iso8601Z: return ISO8601Z_TIME_FMT;
}
return "";
}
#else
auto put_time(const std::tm *tm, const char *fmt) -> decltype (std::put_time(tm, fmt))
namespace __get_put_time_emulation {
// FIXME: Implementations with the cpp11 put_time and get_time either not
// compile or do not pass the tests on the build server. If we switch to newer
// compilers, this namespace can be deleted with all its content.
#ifdef _MSC_VER
// VS2019 implementation fails with ISO8601Z_TIME_FMT.
// VS2019 does not have std::strptime either. See bug:
// https://developercommunity.visualstudio.com/content/problem/140618/c-stdget-time-not-parsing-correctly.html
static const std::map<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
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;
if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6)
return time_t(-1);
struct tm tms;
tms.tm_year = y - 1900; // Year since 1900
tms.tm_mon = M - 1; // 0-11
tms.tm_mday = d; // 1-31
tms.tm_hour = h; // 0-23
tms.tm_min = m; // 0-59
tms.tm_sec = s; // 0-61 (0-60 in C++11)
return {tms, fmt};
}
std::istream &operator>>(std::istream &stream, GetTimeReturnT &&gt)
{
std::string line;
std::getline(stream, line);
if (strptime(line.c_str(), gt.fmt, gt.tms) == nullptr)
stream.setstate(std::ios::failbit);
return stream;
}
inline GetTimeReturnT get_time(std::tm *tms, const char *fmt)
{
return {tms, fmt};
}
}
namespace {
// Platform independent versions of gmtime and localtime. Completely thread
// safe only on Linux. MSVC gtime_s and localtime_s sets global errno thus not
// thread safe.
struct std::tm * _gmtime_r(const time_t *timep, struct tm *result)
{
assert(timep != nullptr && result != nullptr);
#ifdef WIN32
return _mkgmtime(&tms);
time_t t = *timep;
gmtime_s(result, &t);
return result;
#else
return gmtime_r(timep, result);
#endif
}
struct std::tm * _localtime_r(const time_t *timep, struct tm *result)
{
assert(timep != nullptr && result != nullptr);
#ifdef WIN32
// Converts a time_t time value to a tm structure, and corrects for the
// local time zone.
time_t t = *timep;
localtime_s(result, &t);
return result;
#else
return localtime_r(timep, result);
#endif
}
time_t _mktime(const struct std::tm *tms)
{
assert(tms != nullptr);
std::tm _tms = *tms;
return mktime(&_tms);
}
time_t _timegm(const struct std::tm *tms)
{
std::tm _tms = *tms;
#ifdef WIN32
return _mkgmtime(&_tms);
#else /* WIN32 */
return timegm(&tms);
return timegm(&_tms);
#endif /* WIN32 */
}
std::string format_time_ISO8601Z(time_t time)
std::string process_format(const char *fmt, TimeZone zone)
{
struct tm tms;
#ifdef WIN32
gmtime_s(&tms, &time);
#else
gmtime_r(&time, &tms);
#endif
char buf[128];
sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ",
tms.tm_year + 1900,
tms.tm_mon + 1,
tms.tm_mday,
tms.tm_hour,
tms.tm_min,
tms.tm_sec);
return buf;
std::string fmtstr(fmt);
if (fmtstr == SLICER_UTC_TIME_FMT && zone == TimeZone::utc)
fmtstr += " UTC";
return fmtstr;
}
std::string format_local_date_time(time_t time)
{
struct tm tms;
#ifdef WIN32
// Converts a time_t time value to a tm structure, and corrects for the local time zone.
localtime_s(&tms, &time);
#else
localtime_r(&time, &tms);
#endif
char buf[80];
strftime(buf, 80, "%x %X", &tms);
return buf;
}
} // namespace
time_t get_current_time_utc()
{
@ -95,24 +178,57 @@ time_t get_current_time_utc()
return clk::to_time_t(clk::now());
}
static std::string tm2str(const std::tm *tm, const char *fmt)
static std::string tm2str(const std::tm *tms, const char *fmt)
{
std::stringstream ss;
ss << put_time(tm, fmt);
ss.imbue(std::locale("C"));
ss << __get_put_time_emulation::put_time(tms, fmt);
return ss.str();
}
std::string time2str(const time_t &t, TimeZone zone, const char *fmt)
std::string time2str(const time_t &t, TimeZone zone, TimeFormat fmt)
{
std::string ret;
std::tm tms = {};
tms.tm_isdst = -1;
std::string fmtstr = process_format(get_fmtstr(fmt), zone);
switch (zone) {
case TimeZone::local: ret = tm2str(std::localtime(&t), fmt); break;
case TimeZone::utc: ret = tm2str(std::gmtime(&t), fmt) + " UTC"; break;
case TimeZone::local:
ret = tm2str(_localtime_r(&t, &tms), fmtstr.c_str()); break;
case TimeZone::utc:
ret = tm2str(_gmtime_r(&t, &tms), fmtstr.c_str()); break;
}
return ret;
}
static time_t str2time(std::istream &stream, TimeZone zone, const char *fmt)
{
std::tm tms = {};
tms.tm_isdst = -1;
stream >> __get_put_time_emulation::get_time(&tms, fmt);
time_t ret = time_t(-1);
switch (zone) {
case TimeZone::local: ret = _mktime(&tms); break;
case TimeZone::utc: ret = _timegm(&tms); break;
}
if (stream.fail() || ret < time_t(0)) ret = time_t(-1);
return ret;
}
time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt)
{
std::string fmtstr = process_format(get_fmtstr(fmt), zone).c_str();
std::stringstream ss(str);
ss.imbue(std::locale("C"));
return str2time(ss, zone, fmtstr.c_str());
}
}; // namespace Utils
}; // namespace Slic3r

View File

@ -7,41 +7,61 @@
namespace Slic3r {
namespace Utils {
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
// useful for putting timestamps into file and directory names.
// Returns (time_t)-1 on error.
time_t parse_time_ISO8601Z(const std::string &s);
std::string format_time_ISO8601Z(time_t time);
// Format the date and time from an UTC time according to the active locales and a local time zone.
// TODO: make sure time2str is a suitable replacement
std::string format_local_date_time(time_t time);
// There is no gmtime() on windows.
// Should be thread safe.
time_t get_current_time_utc();
const constexpr char *const SLIC3R_TIME_FMT = "%Y-%m-%d at %T";
enum class TimeZone { local, utc };
enum class TimeFormat { gcode, iso8601Z };
std::string time2str(const time_t &t, TimeZone zone, const char *fmt = SLIC3R_TIME_FMT);
// time_t to string functions...
inline std::string current_time2str(TimeZone zone, const char *fmt = SLIC3R_TIME_FMT)
std::string time2str(const time_t &t, TimeZone zone, TimeFormat fmt);
inline std::string time2str(TimeZone zone, TimeFormat fmt)
{
return time2str(get_current_time_utc(), zone, fmt);
}
inline std::string current_local_time2str(const char * fmt = SLIC3R_TIME_FMT)
inline std::string utc_timestamp(time_t t)
{
return current_time2str(TimeZone::local, fmt);
return time2str(t, TimeZone::utc, TimeFormat::gcode);
}
inline std::string current_utc_time2str(const char * fmt = SLIC3R_TIME_FMT)
inline std::string utc_timestamp()
{
return current_time2str(TimeZone::utc, fmt);
return utc_timestamp(get_current_time_utc());
}
}; // namespace Utils
}; // namespace Slic3r
// String to time_t function. Returns time_t(-1) if fails to parse the input.
time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt);
// /////////////////////////////////////////////////////////////////////////////
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
// useful for putting timestamps into file and directory names.
// Returns (time_t)-1 on error.
// Use these functions to convert safely to and from the ISO8601 format on
// all platforms
inline std::string iso_utc_timestamp(time_t t)
{
return time2str(t, TimeZone::utc, TimeFormat::iso8601Z);
}
inline std::string iso_utc_timestamp()
{
return iso_utc_timestamp(get_current_time_utc());
}
inline time_t parse_iso_utc_timestamp(const std::string &str)
{
return str2time(str, TimeZone::utc, TimeFormat::iso8601Z);
}
// /////////////////////////////////////////////////////////////////////////////
} // namespace Utils
} // namespace Slic3r
#endif /* slic3r_Utils_Time_hpp_ */

View File

@ -236,7 +236,7 @@ bool TriangleMesh::needed_repair() const
|| this->stl.stats.backwards_edges > 0;
}
void TriangleMesh::WriteOBJFile(const char* output_file)
void TriangleMesh::WriteOBJFile(const char* output_file) const
{
its_write_obj(this->its, output_file);
}

View File

@ -31,7 +31,7 @@ public:
float volume();
void check_topology();
bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; }
void WriteOBJFile(const char* output_file);
void WriteOBJFile(const char* output_file) const;
void scale(float factor);
void scale(const Vec3d &versor);
void translate(float x, float y, float z);

View File

@ -5,6 +5,7 @@
#include <utility>
#include <functional>
#include <type_traits>
#include <system_error>
#include "libslic3r.h"

View File

@ -543,7 +543,7 @@ std::string string_printf(const char *format, ...)
std::string header_slic3r_generated()
{
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::current_utc_time2str();
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
}
unsigned get_current_pid()

View File

@ -66,7 +66,7 @@ void Snapshot::load_ini(const std::string &path)
if (kvp.first == "id")
this->id = kvp.second.data();
else if (kvp.first == "time_captured") {
this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data());
this->time_captured = Slic3r::Utils::parse_iso_utc_timestamp(kvp.second.data());
if (this->time_captured == (time_t)-1)
throw_on_parse_error("invalid timestamp");
} else if (kvp.first == "slic3r_version_captured") {
@ -165,7 +165,7 @@ void Snapshot::save_ini(const std::string &path)
// Export the common "snapshot".
c << std::endl << "[snapshot]" << std::endl;
c << "id = " << this->id << std::endl;
c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl;
c << "time_captured = " << Slic3r::Utils::iso_utc_timestamp(this->time_captured) << std::endl;
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
c << "comment = " << this->comment << std::endl;
c << "reason = " << reason_string(this->reason) << std::endl;
@ -365,7 +365,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
Snapshot snapshot;
// Snapshot header.
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured);
snapshot.id = Slic3r::Utils::iso_utc_timestamp(snapshot.time_captured);
snapshot.slic3r_version_captured = Slic3r::SEMVER;
snapshot.comment = comment;
snapshot.reason = reason;
@ -393,9 +393,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
for (const VendorProfile &vp : bundle.vendors)
if (vp.id == cfg.name)
cfg.version.config_version = vp.config_version;
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
// Fill-in the min/max slic3r version from the config index, if possible.
try {
// Load the config index for the vendor.

View File

@ -417,7 +417,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const
}
bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); }
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
std::vector<int> GLVolumeCollection::load_object(
const ModelObject *model_object,
@ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary(
TriangleMesh convex_hull = mesh.convex_hull_3d();
for (const std::pair<size_t, size_t>& instance_idx : instances) {
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
GLVolume& v = *this->volumes.back();
v.indexed_vertex_array.load_mesh(mesh);
v.indexed_vertex_array.finalize_geometry(opengl_initialized);

View File

@ -28,6 +28,9 @@ static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
void AppConfig::reset()
{
m_storage.clear();

View File

@ -80,6 +80,12 @@ public:
}
}
bool has_section(const std::string &section) const
{ return m_storage.find(section) != m_storage.end(); }
const std::map<std::string, std::string>& get_section(const std::string &section) const
{ return m_storage.find(section)->second; }
void set_section(const std::string &section, const std::map<std::string, std::string>& data)
{ m_storage[section] = data; }
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
@ -125,6 +131,8 @@ public:
std::vector<std::string> get_recent_projects() const;
void set_recent_projects(const std::vector<std::string>& recent_projects);
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;

View File

@ -349,15 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
toggle_field("pad_wall_thickness", pad_en);
toggle_field("pad_wall_height", pad_en);
toggle_field("pad_brim_size", pad_en);
toggle_field("pad_max_merge_distance", pad_en);
// toggle_field("pad_edge_radius", supports_en);
toggle_field("pad_wall_slope", pad_en);
toggle_field("pad_around_object", pad_en);
toggle_field("pad_around_object_everywhere", pad_en);
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
toggle_field("support_object_elevation", supports_en && !zero_elev);
toggle_field("pad_object_gap", zero_elev);
toggle_field("pad_around_object_everywhere", zero_elev);
toggle_field("pad_object_connector_stride", zero_elev);
toggle_field("pad_object_connector_width", zero_elev);
toggle_field("pad_object_connector_penetration", zero_elev);

View File

@ -35,9 +35,14 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
text += "\">";
text += "<td>";
static const constexpr char *LOCALE_TIME_FMT = "%x %X";
wxString datetime = wxDateTime(snapshot.time_captured).Format(LOCALE_TIME_FMT);
// Format the row header.
text += wxString("<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())
text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
text += "</b></font><br>";

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,15 @@ public:
RR_USER, // User requested the Wizard from the menus
};
ConfigWizard(wxWindow *parent, RunReason run_reason);
// What page should wizard start on
enum StartPage {
SP_WELCOME,
SP_PRINTERS,
SP_FILAMENTS,
SP_MATERIALS,
};
ConfigWizard(wxWindow *parent);
ConfigWizard(ConfigWizard &&) = delete;
ConfigWizard(const ConfigWizard &) = delete;
ConfigWizard &operator=(ConfigWizard &&) = delete;
@ -34,7 +42,7 @@ public:
~ConfigWizard();
// Run the Wizard. Return whether it was completed.
bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
bool run(RunReason reason, StartPage start_page = SP_WELCOME);
static const wxString& name(const bool from_menu = false);

View File

@ -15,11 +15,14 @@
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/listbox.h>
#include <wx/checklst.h>
#include <wx/radiobut.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "AppConfig.hpp"
#include "Preset.hpp"
#include "PresetBundle.hpp"
#include "BedShapeDialog.hpp"
namespace fs = boost::filesystem;
@ -41,6 +44,76 @@ enum {
ROW_SPACING = 75,
};
// Configuration data structures extensions needed for the wizard
enum Technology {
// Bitflag equivalent of PrinterTechnology
T_FFF = 0x1,
T_SLA = 0x2,
T_ANY = ~0,
};
struct Materials
{
Technology technology;
std::set<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;
struct PrinterPicker: wxPanel
@ -61,19 +134,22 @@ struct PrinterPicker: wxPanel
std::vector<Checkbox*> cboxes;
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_vendors);
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter);
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig);
void select_all(bool select, bool alternates = false);
void select_one(size_t i, bool select);
void on_checkbox(const Checkbox *cbox, bool checked);
bool any_selected() const;
int get_width() const { return width; }
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
static const std::string PRINTER_PLACEHOLDER;
private:
int width;
std::vector<int> m_button_indexes;
void on_checkbox(const Checkbox *cbox, bool checked);
};
struct ConfigWizardPage: wxPanel
@ -87,43 +163,107 @@ struct ConfigWizardPage: wxPanel
virtual ~ConfigWizardPage();
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);
return thing;
}
void append_text(wxString text);
wxStaticText* append_text(wxString text);
void append_spacer(int space);
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
virtual void apply_custom_config(DynamicPrintConfig &config) {}
virtual void set_run_reason(ConfigWizard::RunReason run_reason) {}
virtual void on_activate() {}
};
struct PageWelcome: ConfigWizardPage
{
wxStaticText *welcome_text;
wxCheckBox *cbox_reset;
PageWelcome(ConfigWizard *parent);
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
};
struct PagePrinters: ConfigWizardPage
{
enum Technology {
// Bitflag equivalent of PrinterTechnology
T_FFF = 0x1,
T_SLA = 0x2,
T_Any = ~0,
};
std::vector<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);
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
@ -150,13 +290,22 @@ struct PageUpdate: ConfigWizardPage
PageUpdate(ConfigWizard *parent);
};
struct PageMode: ConfigWizardPage
{
wxRadioButton *radio_simple;
wxRadioButton *radio_advanced;
wxRadioButton *radio_expert;
PageMode(ConfigWizard *parent);
void serialize_mode(AppConfig *app_config) const;
virtual void on_activate();
};
struct PageVendors: ConfigWizardPage
{
std::vector<PrinterPicker*> pickers;
PageVendors(ConfigWizard *parent);
void on_vendor_pick(size_t i);
};
struct PageFirmware: ConfigWizardPage
@ -194,6 +343,8 @@ struct PageTemperatures: ConfigWizardPage
virtual void apply_custom_config(DynamicPrintConfig &config);
};
typedef std::map<std::string /* = vendor ID */, PagePrinters*> Pages3rdparty;
class ConfigWizardIndex: public wxPanel
{
@ -210,12 +361,14 @@ public:
void go_prev();
void go_next();
void go_to(size_t i);
void go_to(ConfigWizardPage *page);
void go_to(const ConfigWizardPage *page);
void clear();
void msw_rescale();
int em() const { return em_w; }
static const size_t NO_ITEM = size_t(-1);
private:
struct Item
{
@ -228,12 +381,6 @@ private:
int em_w;
int em_h;
/* #ys_FIXME_delete_after_testing by VK
const wxBitmap bg;
const wxBitmap bullet_black;
const wxBitmap bullet_blue;
const wxBitmap bullet_white;
*/
ScalableBitmap bg;
ScalableBitmap bullet_black;
ScalableBitmap bullet_blue;
@ -245,9 +392,6 @@ private:
ssize_t item_hover;
size_t last_page;
/* #ys_FIXME_delete_after_testing by VK
int item_height() const { return std::max(bullet_black.GetSize().GetHeight(), em_w) + em_w; }
*/
int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; }
void on_paint(wxPaintEvent &evt);
@ -256,14 +400,24 @@ private:
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
// ConfigWizard private data
struct ConfigWizard::priv
{
ConfigWizard *q;
ConfigWizard::RunReason run_reason;
AppConfig appconfig_vendors;
std::unordered_map<std::string, VendorProfile> vendors;
std::unordered_map<std::string, std::string> vendors_rsrc;
std::unique_ptr<DynamicPrintConfig> custom_config;
ConfigWizard::RunReason run_reason = RR_USER;
AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI
BundleMap bundles; // Holds all loaded config bundles, the key is the vendor names.
// Materials refers to Presets in those bundles by pointers.
// Also we update the is_visible flag in printer Presets according to the
// PrinterPickers state.
Materials filaments; // Holds available filament presets and their types & vendors
Materials sla_materials; // Ditto for SLA materials
std::unique_ptr<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;
wxBoxSizer *hscroll_sizer = nullptr;
@ -279,9 +433,13 @@ struct ConfigWizard::priv
PageWelcome *page_welcome = nullptr;
PagePrinters *page_fff = nullptr;
PagePrinters *page_msla = nullptr;
PageMaterials *page_filaments = nullptr;
PageMaterials *page_sla_materials = nullptr;
PageCustom *page_custom = nullptr;
PageUpdate *page_update = nullptr;
PageVendors *page_vendors = nullptr; // XXX: ?
PageMode *page_mode = nullptr;
PageVendors *page_vendors = nullptr;
Pages3rdparty pages_3rdparty;
// Custom setup pages
PageFirmware *page_firmware = nullptr;
@ -289,17 +447,30 @@ struct ConfigWizard::priv
PageDiameters *page_diams = nullptr;
PageTemperatures *page_temps = nullptr;
priv(ConfigWizard *q) : q(q) {}
// Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex)
std::vector<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();
bool check_first_variant() const;
void load_vendors();
void add_page(ConfigWizardPage *page);
void enable_next(bool enable);
void set_start_page(ConfigWizard::StartPage start_page);
void create_3rdparty_pages();
void set_run_reason(RunReason run_reason);
void update_materials(Technology technology);
void on_custom_setup(bool custom_wanted);
void on_custom_setup();
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);

View File

@ -1747,101 +1747,114 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
_set_current();
struct ModelVolumeState {
ModelVolumeState(const GLVolume *volume) :
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) :
model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) :
model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
bool new_geometry() const { return this->volume_idx == size_t(-1); }
const ModelVolume *model_volume;
ModelVolumeState(const GLVolume* volume) :
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) :
model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) :
model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
bool new_geometry() const { return this->volume_idx == size_t(-1); }
const ModelVolume* model_volume;
// ObjectID of ModelVolume + ObjectID of ModelInstance
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
std::pair<size_t, size_t> geometry_id;
GLVolume::CompositeID composite_id;
// Volume index in the new GLVolume vector.
size_t volume_idx;
size_t volume_idx;
};
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.
typedef std::array<SLAPrintObjectStep, 2> SLASteps;
SLASteps sla_steps = { slaposSupportTree, slaposBasePool };
SLASteps sla_steps = { slaposSupportTree, slaposPad };
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.
std::vector<SLASupportState> sla_support_state;
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<GLVolumeState> deleted_volumes;
std::vector<GLVolume*> glvolumes_new;
glvolumes_new.reserve(m_volumes.volumes.size());
auto model_volume_state_lower = [](const ModelVolumeState &m1, const ModelVolumeState &m2) { return m1.geometry_id < m2.geometry_id; };
auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; };
m_reload_delayed = ! m_canvas->IsShown() && ! refresh_immediately && ! force_full_scene_refresh;
m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh;
PrinterTechnology printer_technology = m_process->current_printer_technology();
PrinterTechnology printer_technology = m_process->current_printer_technology();
int volume_idx_wipe_tower_old = -1;
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
// First initialize model_volumes_new_sorted & model_instances_new_sorted.
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) {
const ModelObject *model_object = m_model->objects[object_idx];
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) {
const ModelInstance *model_instance = model_object->instances[instance_idx];
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) {
const ModelVolume *model_volume = model_object->volumes[volume_idx];
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) {
const ModelObject* model_object = m_model->objects[object_idx];
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) {
const ModelInstance* model_instance = model_object->instances[instance_idx];
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) {
const ModelVolume* model_volume = model_object->volumes[volume_idx];
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
}
}
}
if (printer_technology == ptSLA) {
const SLAPrint *sla_print = this->sla_print();
#ifndef NDEBUG
const SLAPrint* sla_print = this->sla_print();
#ifndef NDEBUG
// Verify that the SLAPrint object is synchronized with m_model.
check_model_ids_equal(*m_model, sla_print->model());
#endif /* NDEBUG */
#endif /* NDEBUG */
sla_support_state.reserve(sla_print->objects().size());
for (const SLAPrintObject *print_object : sla_print->objects()) {
for (const SLAPrintObject* print_object : sla_print->objects()) {
SLASupportState state;
for (size_t istep = 0; istep < sla_steps.size(); ++ istep) {
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
if (state.step[istep].state == PrintStateBase::DONE) {
if (! print_object->has_mesh(sla_steps[istep]))
for (size_t istep = 0; istep < sla_steps.size(); ++istep) {
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
if (state.step[istep].state == PrintStateBase::DONE) {
if (!print_object->has_mesh(sla_steps[istep]))
// Consider the DONE step without a valid mesh as invalid for the purpose
// of mesh visualization.
state.step[istep].state = PrintStateBase::INVALID;
else
for (const ModelInstance *model_instance : print_object->model_object()->instances)
// Only the instances, which are currently printable, will have the SLA support structures kept.
// The instances outside the print bed will have the GLVolumes of their support structures released.
if (model_instance->is_printable())
for (const ModelInstance* model_instance : print_object->model_object()->instances)
// Only the instances, which are currently printable, will have the SLA support structures kept.
// The instances outside the print bed will have the GLVolumes of their support structures released.
if (model_instance->is_printable())
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
}
}
sla_support_state.emplace_back(state);
}
sla_support_state.emplace_back(state);
}
}
std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower);
std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower);
// Release all ModelVolume based GLVolumes not found in the current Model.
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) {
GLVolume *volume = m_volumes.volumes[volume_id];
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) {
GLVolume* volume = m_volumes.volumes[volume_id];
ModelVolumeState key(volume);
ModelVolumeState *mvs = nullptr;
ModelVolumeState* mvs = nullptr;
if (volume->volume_idx() < 0) {
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id)
// This can be an SLA support structure that should not be rendered (in case someone used undo
// to revert to before it was generated). We only reuse the volume if that's not the case.
if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints)
mvs = &(*it);
} else {
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
}
else {
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
mvs = &(*it);
mvs = &(*it);
}
// Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID.
// The wipe tower has its own wipe_tower_instance_id().
@ -1854,19 +1867,23 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
assert(volume_idx_wipe_tower_old == -1);
volume_idx_wipe_tower_old = (int)volume_id;
}
if (! m_reload_delayed)
if (!m_reload_delayed)
{
deleted_volumes.emplace_back(volume, volume_id);
delete volume;
} else {
}
}
else {
// This GLVolume will be reused.
volume->set_sla_shift_z(0.0);
map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
mvs->volume_idx = glvolumes_new.size();
glvolumes_new.emplace_back(volume);
// Update color of the volume based on the current extruder.
if (mvs->model_volume != nullptr) {
int extruder_id = mvs->model_volume->extruder_id();
if (extruder_id != -1)
volume->extruder_id = extruder_id;
if (mvs->model_volume != nullptr) {
int extruder_id = mvs->model_volume->extruder_id();
if (extruder_id != -1)
volume->extruder_id = extruder_id;
volume->is_modifier = !mvs->model_volume->is_model_part();
volume->set_color_from_model_volume(mvs->model_volume);
@ -1884,6 +1901,16 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
bool update_object_list = false;
auto find_old_volume_id = [&deleted_volumes](const GLVolume::CompositeID& id) -> unsigned int {
for (unsigned int i = 0; i < (unsigned int)deleted_volumes.size(); ++i)
{
const GLVolumeState& v = deleted_volumes[i];
if (v.composite_id == id)
return v.volume_idx;
}
return (unsigned int)-1;
};
if (m_volumes.volumes != glvolumes_new)
update_object_list = true;
m_volumes.volumes = std::move(glvolumes_new);
@ -1898,9 +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);
if (it->new_geometry()) {
// 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.volumes.back()->geometry_id = key.geometry_id;
update_object_list = true;
update_object_list = true;
} else {
// Recycling an old GLVolume.
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
@ -1999,19 +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;
const Print *print = m_process->fff_print();
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4);
double nozzle_diameter = print->config().nozzle_diameter.values[0];
float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth;
float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width;
if (!print->is_step_done(psWipeTower))
depth = (900.f/w) * (float)(extruders_count - 1);
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
brim_spacing * 4.5f, m_initialized);
brim_width, m_initialized);
if (volume_idx_wipe_tower_old != -1)
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
@ -2634,19 +2662,6 @@ std::string format_mouse_event_debug_message(const wxMouseEvent &evt)
void GLCanvas3D::on_mouse(wxMouseEvent& evt)
{
auto mouse_up_cleanup = [this](){
m_moving = false;
m_mouse.drag.move_volume_idx = -1;
m_mouse.set_start_position_3D_as_invalid();
m_mouse.set_start_position_2D_as_invalid();
m_mouse.dragging = false;
m_mouse.ignore_left_up = false;
m_dirty = true;
if (m_canvas->HasCapture())
m_canvas->ReleaseMouse();
};
#if ENABLE_RETINA_GL
const float scale = m_retina_helper->get_scale_factor();
evt.SetX(evt.GetX() * scale);
@ -3483,6 +3498,20 @@ void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
m_volumes.export_toolpaths_to_obj(filename);
}
void GLCanvas3D::mouse_up_cleanup()
{
m_moving = false;
m_mouse.drag.move_volume_idx = -1;
m_mouse.set_start_position_3D_as_invalid();
m_mouse.set_start_position_2D_as_invalid();
m_mouse.dragging = false;
m_mouse.ignore_left_up = false;
m_dirty = true;
if (m_canvas->HasCapture())
m_canvas->ReleaseMouse();
}
bool GLCanvas3D::_is_shown_on_screen() const
{
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
@ -5284,20 +5313,18 @@ void GLCanvas3D::_load_fff_shells()
// adds wipe tower's volume
double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
const PrintConfig& config = print->config();
unsigned int extruders_count = config.nozzle_diameter.size();
size_t extruders_count = config.nozzle_diameter.size();
if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) {
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4);
double nozzle_diameter = print->config().nozzle_diameter.values[0];
float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth;
float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width;
if (!print->is_step_done(psWipeTower))
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1);
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
!print->is_step_done(psWipeTower), brim_spacing * 4.5f, m_initialized);
!print->is_step_done(psWipeTower), brim_width, m_initialized);
}
}
}
@ -5340,8 +5367,8 @@ void GLCanvas3D::_load_sla_shells()
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool))
add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad))
add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
}
double shift_z = obj->get_current_elevation();
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {

View File

@ -624,6 +624,8 @@ public:
bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj(const char* filename) const;
void mouse_up_cleanup();
private:
bool _is_shown_on_screen() const;

View File

@ -108,6 +108,8 @@ void GLCanvas3DManager::GLInfo::detect() const
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
m_max_tex_size /= 2;
if (GLEW_EXT_texture_filter_anisotropic)
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy));

View File

@ -107,8 +107,8 @@ void GLTexture::Compressor::compress()
break;
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
// crashes if doing so, so we start with twice the required size
level.compressed_data = std::vector<unsigned char>(level.w * level.h * 2, 0);
// crashes if doing so, requiring a minimum of 16 bytes and up to a third of the source buffer size, so we set the destination buffer initial size to be half the source buffer size
level.compressed_data = std::vector<unsigned char>(std::max((unsigned int)16, level.w * level.h * 2), 0);
int compressed_size = 0;
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
level.compressed_data.resize(compressed_size);
@ -455,8 +455,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
// we do not need to generate all levels down to 1x1
while ((lod_w > 16) || (lod_h > 16))
while ((lod_w > 1) || (lod_h > 1))
{
++level;
@ -600,8 +599,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
// we do not need to generate all levels down to 1x1
while ((lod_w > 16) || (lod_h > 16))
while ((lod_w > 1) || (lod_h > 1))
{
++level;

View File

@ -101,49 +101,6 @@ const std::string& shortkey_alt_prefix()
return str;
}
bool config_wizard_startup(bool app_config_exists)
{
if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) {
config_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (get_app_config()->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
config_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}
return false;
}
void config_wizard(int reason)
{
// Exit wizard if there are unsaved changes and the user cancels the action.
if (! wxGetApp().check_unsaved_changes())
return;
try {
ConfigWizard wizard(nullptr, static_cast<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)
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
{

View File

@ -35,14 +35,6 @@ extern AppConfig* get_app_config();
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
// Checks if configuration wizard needs to run, calls config_wizard if so.
// Returns whether the Wizard ran.
extern bool config_wizard_startup(bool app_config_exists);
// Opens the configuration wizard, returns true if wizard is finished & accepted.
// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl.
extern void config_wizard(int run_reason);
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);

View File

@ -38,7 +38,6 @@
#include "../Utils/PresetUpdater.hpp"
#include "../Utils/PrintHost.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "ConfigWizard.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp"
@ -46,6 +45,7 @@
#include "Tab.hpp"
#include "SysInfoDialog.hpp"
#include "KBShortcutsDialog.hpp"
#include "UpdateDialogs.hpp"
#ifdef __WXMSW__
#include <Shlobj.h>
@ -148,6 +148,7 @@ GUI_App::GUI_App()
: wxApp()
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_wizard(nullptr)
{}
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
preset_bundle->setup_directories();
app_conf_exists = app_config->exists();
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
@ -287,7 +287,7 @@ bool GUI_App::on_init_inner()
}
CallAfter([this] {
config_wizard_startup(app_conf_exists);
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
@ -826,7 +826,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
switch (event.GetId() - config_id_base) {
case ConfigMenuWizard:
config_wizard(ConfigWizard::RR_USER);
run_wizard(ConfigWizard::RR_USER);
break;
case ConfigMenuTakeSnapshot:
// Take a configuration snapshot.
@ -1057,6 +1057,31 @@ void GUI_App::open_web_page_localized(const std::string &http_address)
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe());
}
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (! m_wizard) {
m_wizard = new ConfigWizard(mainframe);
}
const bool res = m_wizard->run(reason, start_page);
if (res) {
load_current_presets();
if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA
&& Slic3r::model_has_multi_part_objects(wxGetApp().model())) {
GUI::show_info(nullptr,
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
_(L("Please check and fix your object list.")),
_(L("Attention!")));
}
}
return res;
}
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
{
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
// void warning_catcher{
// my($self, $message_dialog) = @_;

View File

@ -6,6 +6,7 @@
#include "libslic3r/PrintConfig.hpp"
#include "MainFrame.hpp"
#include "ImGuiWrapper.hpp"
#include "ConfigWizard.hpp"
#include <wx/app.h>
#include <wx/colour.h>
@ -69,6 +70,7 @@ enum ConfigMenuIDs {
};
class Tab;
class ConfigWizard;
static wxString dots("", wxConvUTF8);
@ -96,6 +98,7 @@ class GUI_App : public wxApp
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
ConfigWizard* m_wizard; // Managed by wxWindow tree
public:
bool OnInit() override;
@ -184,6 +187,7 @@ public:
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
void open_web_page_localized(const std::string &http_address);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
private:
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_sanitize(wxTopLevelWindow* window);
bool select_language();
bool config_wizard_startup();
#ifdef __WXMSW__
void associate_3mf_files();
#endif // __WXMSW__

View File

@ -267,7 +267,8 @@ void ObjectList::create_objects_ctrl()
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
// column Extruder of the view control:
AppendColumn(create_objects_list_extruder_column(4));
AppendColumn(new wxDataViewColumn(_(L("Extruder")), new BitmapChoiceRenderer(),
colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE));
// column ItemEditing of the view control:
AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
@ -434,19 +435,6 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons
(*m_objects)[obj_idx]->config;
}
wxDataViewColumn* ObjectList::create_objects_list_extruder_column(size_t extruders_count)
{
wxArrayString choices;
choices.Add(_(L("default")));
for (int i = 1; i <= extruders_count; ++i)
choices.Add(wxString::Format("%d", i));
wxDataViewChoiceRenderer *c =
new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL);
wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, colExtruder,
8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
return column;
}
void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
{
for (size_t i = 0; i < m_objects->size(); ++i)
@ -462,7 +450,7 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
else
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) {
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
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)
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;
if (m_objects && extruders_count > 1)
update_extruder_values_for_items(extruders_count);
// delete old extruder column
DeleteColumn(GetColumn(colExtruder));
// insert new created extruder column
InsertColumn(colExtruder, create_objects_list_extruder_column(extruders_count));
update_extruder_colors();
// set show/hide for this column
set_extruder_column_hidden(extruders_count <= 1);
//a workaround for a wrong last column width updating under OSX
@ -507,6 +489,11 @@ void ObjectList::update_objects_list_extruder_column(size_t extruders_count)
m_prevent_update_extruder_in_config = false;
}
void ObjectList::update_extruder_colors()
{
m_objects_model->UpdateColumValues(colExtruder);
}
void ObjectList::set_extruder_column_hidden(const bool hide) const
{
GetColumn(colExtruder)->SetHidden(hide);
@ -535,14 +522,10 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
m_config = &get_item_config(item);
}
wxVariant variant;
m_objects_model->GetValue(variant, item, colExtruder);
const wxString selection = variant.GetString();
if (!m_config || selection.empty())
if (!m_config)
return;
const int extruder = /*selection.size() > 1 ? 0 : */atoi(selection.c_str());
const int extruder = m_objects_model->GetExtruderNumber(item);
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
// update scene
@ -795,7 +778,13 @@ void ObjectList::OnChar(wxKeyEvent& event)
void ObjectList::OnContextMenu(wxDataViewEvent&)
{
list_manipulation(true);
// Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true;
if (!evt_context_menu)
canvas->mouse_up_cleanup();
list_manipulation(evt_context_menu);
}
void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
@ -805,6 +794,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
const wxPoint pt = get_mouse_position_in_control();
HitTest(pt, item, col);
if (m_extruder_editor)
m_extruder_editor->Hide();
/* Note: Under OSX right click doesn't send "selection changed" event.
* It means that Selection() will be return still previously selected item.
* Thus under OSX we should force UnselectAll(), when item and col are nullptr,
@ -853,6 +845,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
fix_through_netfabb();
}
}
// workaround for extruder editing under OSX
else if (wxOSX && evt_context_menu && title == _("Extruder"))
extruder_editing();
#ifndef __WXMSW__
GetMainWindow()->SetToolTip(""); // hide tooltip
@ -894,6 +889,74 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
wxGetApp().plater()->PopupMenu(menu);
}
void ObjectList::extruder_editing()
{
wxDataViewItem item = GetSelection();
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
return;
std::vector<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()
{
// if (m_selection_mode & smLayer)
@ -1514,6 +1577,12 @@ void ObjectList::append_menu_item_export_stl(wxMenu* menu) const
menu->AppendSeparator();
}
void ObjectList::append_menu_item_reload_from_disk(wxMenu* menu) const
{
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")),
[this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater());
}
void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const
{
const wxString name = _(L("Change extruder"));
@ -1563,6 +1632,7 @@ void ObjectList::create_object_popupmenu(wxMenu *menu)
append_menu_items_osx(menu);
#endif // __WXOSX__
append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_scale_selection_to_fit_print_volume(menu);
@ -1586,6 +1656,7 @@ void ObjectList::create_sla_object_popupmenu(wxMenu *menu)
append_menu_items_osx(menu);
#endif // __WXOSX__
append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
// rest of a object_sla_menu will be added later in:
@ -1598,8 +1669,9 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
append_menu_items_osx(menu);
#endif // __WXOSX__
append_menu_item_fix_through_netfabb(menu);
append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_split(menu);
@ -2259,6 +2331,7 @@ void ObjectList::changed_object(const int obj_idx/* = -1*/) const
void ObjectList::part_selection_changed()
{
if (m_extruder_editor) m_extruder_editor->Hide();
int obj_idx = -1;
int volume_id = -1;
m_config = nullptr;
@ -2341,7 +2414,8 @@ void ObjectList::part_selection_changed()
wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " ");
if (item) {
wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
// wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item));
wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id));
}
}
@ -2547,7 +2621,7 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
(*m_objects)[item->obj_idx]->config.has("extruder"))
{
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);
}
@ -3822,7 +3896,7 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
/* We can change extruder for Object/Volume only.
* So, if Instance is selected, get its Object item and change it
*/
m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, colExtruder);
m_objects_model->SetExtruder(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item);
const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) :
m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));

View File

@ -12,6 +12,7 @@
#include "wxExtensions.hpp"
class wxBoxSizer;
class wxBitmapComboBox;
class wxMenuItem;
class ObjectDataViewModel;
class MenuWithSeparators;
@ -140,6 +141,8 @@ private:
DynamicPrintConfig *m_config {nullptr};
std::vector<ModelObject*> *m_objects{ nullptr };
wxBitmapComboBox *m_extruder_editor { nullptr };
std::vector<wxBitmap*> m_bmp_vector;
t_layer_config_ranges m_layer_config_ranges_cache;
@ -183,8 +186,8 @@ public:
void create_objects_ctrl();
void create_popup_menus();
wxDataViewColumn* create_objects_list_extruder_column(size_t extruders_count);
void update_objects_list_extruder_column(size_t extruders_count);
void update_extruder_colors();
// show/hide "Extruder" column for Objects List
void set_extruder_column_hidden(const bool hide) const;
// update extruder in current config
@ -210,6 +213,7 @@ public:
void selection_changed();
void show_context_menu(const bool evt_context_menu);
void extruder_editing();
#ifndef __WXOSX__
void key_event(wxKeyEvent& event);
#endif /* __WXOSX__ */
@ -233,7 +237,8 @@ public:
wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent);
void append_menu_items_osx(wxMenu* menu);
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
void append_menu_item_export_stl(wxMenu* menu) const ;
void append_menu_item_export_stl(wxMenu* menu) const;
void append_menu_item_reload_from_disk(wxMenu* menu) const;
void append_menu_item_change_extruder(wxMenu* menu) const;
void append_menu_item_delete(wxMenu* menu);
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);

View File

@ -112,40 +112,33 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo)
combo->SetValue(selection);
}
static void set_font_and_background_style(wxWindow* win, const wxFont& font)
{
win->SetFont(font);
win->SetBackgroundStyle(wxBG_STYLE_PAINT);
}
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
OG_Settings(parent, true)
#ifndef __APPLE__
, m_focused_option("")
#endif // __APPLE__
{
m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation");
m_og->set_name(_(L("Object Manipulation")));
m_og->label_width = 12;//125;
m_og->set_grid_vgap(5);
m_og->m_on_change = std::bind(&ObjectManipulation::on_change, this, std::placeholders::_1, std::placeholders::_2);
m_og->m_fill_empty_value = std::bind(&ObjectManipulation::on_fill_empty_value, this, std::placeholders::_1);
// Load bitmaps to be used for the mirroring buttons:
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off");
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
m_og->m_set_focus = [this](const std::string& opt_key)
{
#ifndef __APPLE__
m_focused_option = opt_key;
#endif // __APPLE__
const int border = wxOSX ? 0 : 4;
const int em = wxGetApp().em_unit();
m_main_grid_sizer = new wxFlexGridSizer(2, 3, 3); // "Name/label", "String name / Editors"
m_main_grid_sizer->SetFlexibleDirection(wxBOTH);
// needed to show the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true);
};
// Add "Name" label with warning icon
auto sizer = new wxBoxSizer(wxHORIZONTAL);
ConfigOptionDef def;
Line line = Line{ "Name", "Object name" };
auto manifold_warning_icon = [this](wxWindow* parent) {
m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
if (is_windows10())
m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e)
m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
if (is_windows10())
m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent& e)
{
// if object/sub-object has no errors
if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData())
@ -155,249 +148,270 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list());
});
return m_fix_throught_netfab_bitmap;
sizer->Add(m_fix_throught_netfab_bitmap);
auto name_label = new wxStaticText(m_parent, wxID_ANY, _(L("Name"))+":");
set_font_and_background_style(name_label, wxGetApp().normal_font());
name_label->SetToolTip(_(L("Object name")));
sizer->Add(name_label);
m_main_grid_sizer->Add(sizer);
// Add name of the item
const wxSize name_size = wxSize(20 * em, wxDefaultCoord);
m_item_name = new wxStaticText(m_parent, wxID_ANY, "", wxDefaultPosition, name_size, wxST_ELLIPSIZE_MIDDLE);
set_font_and_background_style(m_item_name, wxGetApp().bold_font());
m_main_grid_sizer->Add(m_item_name, 0, wxEXPAND);
// Add labels grid sizer
m_labels_grid_sizer = new wxFlexGridSizer(1, 3, 3); // "Name/label", "String name / Editors"
m_labels_grid_sizer->SetFlexibleDirection(wxBOTH);
// Add world local combobox
m_word_local_combo = create_word_local_combo(parent);
m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) {
this->set_world_coordinates(evt.GetSelection() != 1);
}), m_word_local_combo->GetId());
// Small trick to correct layouting in different view_mode :
// Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden
m_word_local_combo_sizer = new wxBoxSizer(wxHORIZONTAL);
m_empty_str = new wxStaticText(parent, wxID_ANY, "");
m_word_local_combo_sizer->Add(m_word_local_combo);
m_word_local_combo_sizer->Add(m_empty_str);
m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1)));
m_labels_grid_sizer->Add(m_word_local_combo_sizer);
// Text trick to grid sizer layout:
// Height of labels should be equivalent to the edit boxes
int height = wxTextCtrl(parent, wxID_ANY, "Br").GetBestHeight(-1);
#ifdef __WXGTK__
// On Linux button with bitmap has bigger height then regular button or regular TextCtrl
// It can cause a wrong alignment on show/hide of a reset buttons
const int bmp_btn_height = ScalableButton(parent, wxID_ANY, "undo") .GetBestHeight(-1);
if (bmp_btn_height > height)
height = bmp_btn_height;
#endif //__WXGTK__
auto add_label = [this, height](wxStaticText** label, const std::string& name, wxSizer* reciver = nullptr)
{
*label = new wxStaticText(m_parent, wxID_ANY, _(name) + ":");
set_font_and_background_style(m_move_Label, wxGetApp().normal_font());
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->SetMinSize(wxSize(-1, height));
sizer->Add(*label, 0, wxALIGN_CENTER_VERTICAL);
if (reciver)
reciver->Add(sizer);
else
m_labels_grid_sizer->Add(sizer);
m_rescalable_sizers.push_back(sizer);
};
line.near_label_widget = manifold_warning_icon;
def.label = "";
def.gui_type = "legend";
def.tooltip = L("Object name");
#ifdef __APPLE__
def.width = 20;
#else
def.width = 22;
#endif
def.set_default_value(new ConfigOptionString{ " " });
line.append_option(Option(def, "object_name"));
m_og->append_line(line);
// Add labels
add_label(&m_move_Label, L("Position"));
add_label(&m_rotate_Label, L("Rotation"));
const int field_width = 5;
// additional sizer for lock and labels "Scale" & "Size"
sizer = new wxBoxSizer(wxHORIZONTAL);
// Mirror button size:
const int mirror_btn_width = 3;
m_lock_bnt = new LockButton(parent, wxID_ANY);
m_lock_bnt->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
event.Skip();
wxTheApp->CallAfter([this]() { set_uniform_scaling(m_lock_bnt->IsLocked()); });
});
sizer->Add(m_lock_bnt, 0, wxALIGN_CENTER_VERTICAL);
// Legend for object modification
line = Line{ "", "" };
def.label = "";
def.type = coString;
def.width = field_width - mirror_btn_width;//field_width/*50*/;
auto v_sizer = new wxGridSizer(1, 3, 3);
// Load bitmaps to be used for the mirroring buttons:
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off");
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
add_label(&m_scale_Label, L("Scale"), v_sizer);
wxStaticText* size_Label {nullptr};
add_label(&size_Label, L("Size"), v_sizer);
if (wxOSX) set_font_and_background_style(size_Label, wxGetApp().normal_font());
sizer->Add(v_sizer, 0, wxLEFT, border);
m_labels_grid_sizer->Add(sizer);
m_main_grid_sizer->Add(m_labels_grid_sizer, 0, wxEXPAND);
// Add editors grid sizer
wxFlexGridSizer* editors_grid_sizer = new wxFlexGridSizer(5, 3, 3); // "Name/label", "String name / Editors"
editors_grid_sizer->SetFlexibleDirection(wxBOTH);
// Add Axes labels with icons
static const char axes[] = { 'X', 'Y', 'Z' };
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
const char label = axes[axis_idx];
def.set_default_value(new ConfigOptionString{ std::string(" ") + label });
Option option(def, std::string() + label + "_axis_legend");
wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label));
set_font_and_background_style(axis_name, wxGetApp().bold_font());
sizer = new wxBoxSizer(wxHORIZONTAL);
// Under OSX we use font, smaller than default font, so
// there is a next trick for an equivalent layout of coordinates combobox and axes labels in they own sizers
if (wxOSX)
sizer->SetMinSize(-1, m_word_local_combo->GetBestHeight(-1));
sizer->Add(axis_name, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
// We will add a button to toggle mirroring to each axis:
auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) {
wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
btn->SetBitmapDisabled_(m_mirror_bitmap_hidden);
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
btn->SetBitmapDisabled_(m_mirror_bitmap_hidden);
m_mirror_buttons[axis_idx].first = btn;
m_mirror_buttons[axis_idx].second = mbShown;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
m_mirror_buttons[axis_idx].first = btn;
m_mirror_buttons[axis_idx].second = mbShown;
btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) {
Axis axis = (Axis)(axis_idx + X);
if (m_mirror_buttons[axis_idx].second == mbHidden)
return;
sizer->AddStretchSpacer(2);
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL);
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) {
Axis axis = (Axis)(axis_idx + X);
if (m_mirror_buttons[axis_idx].second == mbHidden)
return;
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<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()){
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis));
}
}
else
return;
}
else
return;
// Update mirroring at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror(L("Set Mirror"));
UpdateAndShow(true);
});
// Update mirroring at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror(L("Set Mirror"));
UpdateAndShow(true);
});
return sizer;
};
option.side_widget = mirror_button;
line.append_option(option);
editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL);
}
line.near_label_widget = [this](wxWindow* parent) {
wxBitmapComboBox *combo = create_word_local_combo(parent);
combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent &evt) { this->set_world_coordinates(evt.GetSelection() != 1); }), combo->GetId());
m_word_local_combo = combo;
return combo;
};
m_og->append_line(line);
auto add_og_to_object_settings = [this, field_width](const std::string& option_name, const std::string& sidetext)
editors_grid_sizer->AddStretchSpacer(1);
editors_grid_sizer->AddStretchSpacer(1);
// add EditBoxes
auto add_edit_boxes = [this, editors_grid_sizer](const std::string& opt_key, int axis)
{
Line line = { _(option_name), "" };
ConfigOptionDef def;
def.type = coFloat;
def.set_default_value(new ConfigOptionFloat(0.0));
def.width = field_width/*50*/;
ManipulationEditor* editor = new ManipulationEditor(this, opt_key, axis);
m_editors.push_back(editor);
if (option_name == "Scale") {
// Add "uniform scaling" button in front of "Scale" option
line.near_label_widget = [this](wxWindow* parent) {
auto btn = new LockButton(parent, wxID_ANY);
btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){
event.Skip();
wxTheApp->CallAfter([btn, this]() { set_uniform_scaling(btn->IsLocked()); });
});
m_lock_bnt = btn;
return btn;
};
// Add reset scale button
auto reset_scale_button = [this](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
btn->SetToolTip(_(L("Reset scale")));
m_reset_scale_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale")));
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
});
return sizer;
};
line.append_widget(reset_scale_button);
}
else if (option_name == "Rotation") {
// Add reset rotation button
auto reset_rotation_button = [this](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
btn->SetToolTip(_(L("Reset rotation")));
m_reset_rotation_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
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;
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate(L("Reset Rotation"));
UpdateAndShow(true);
});
return sizer;
};
line.append_widget(reset_rotation_button);
}
else if (option_name == "Position") {
// Add drop to bed button
auto drop_to_bed_button = [=](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
btn->SetToolTip(_(L("Drop to bed")));
m_drop_to_bed_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
// ???
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed")));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
});
return sizer;
};
line.append_widget(drop_to_bed_button);
}
// Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
else if (option_name == "Size") {
line.near_label_widget = [this](wxWindow* parent) {
return new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition,
create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize());
};
}
const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
for (const char *axis : { "_x", "_y", "_z" }) {
if (axis[1] == 'z')
def.sidetext = sidetext;
Option option = Option(def, lower_name + axis);
option.opt.full_width = true;
line.append_option(option);
}
return line;
editors_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL);
};
// Settings table
m_og->sidetext_width = 3;
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label);
m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label);
m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label);
m_og->append_line(add_og_to_object_settings(L("Size"), "mm"));
// add Units
auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit)
{
wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
set_font_and_background_style(unit_text, wxGetApp().normal_font());
// call back for a rescale of button "Set uniform scale"
m_og->rescale_near_label_widget = [this](wxWindow* win) {
// rescale lock icon
auto *ctrl = dynamic_cast<LockButton*>(win);
if (ctrl != nullptr) {
ctrl->msw_rescale();
return;
}
// Unit text should be the same height as labels
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->SetMinSize(wxSize(-1, height));
sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL);
if (win == m_fix_throught_netfab_bitmap)
return;
// rescale "place" of the empty icon (to correct layout of the "Size" and "Scale")
if (dynamic_cast<wxStaticBitmap*>(win) != nullptr)
win->SetMinSize(create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize());
editors_grid_sizer->Add(sizer);
m_rescalable_sizers.push_back(sizer);
};
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("position", axis_idx);
add_unit_text(L("mm"));
// Add drop to bed button
m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
m_drop_to_bed_button->SetToolTip(_(L("Drop to bed")));
m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
// ???
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed")));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
});
editors_grid_sizer->Add(m_drop_to_bed_button);
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;
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate(L("Reset Rotation"));
UpdateAndShow(true);
});
editors_grid_sizer->Add(m_reset_rotation_button);
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("scale", axis_idx);
add_unit_text("%");
// Add reset scale button
m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
m_reset_scale_button->SetToolTip(_(L("Reset scale")));
m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale")));
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
});
editors_grid_sizer->Add(m_reset_scale_button);
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("size", axis_idx);
add_unit_text("mm");
editors_grid_sizer->AddStretchSpacer(1);
m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
m_og->sizer->Clear(true);
m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border);
}
void ObjectManipulation::Show(const bool show)
{
if (show != IsShown()) {
@ -407,9 +421,9 @@ void ObjectManipulation::Show(const bool show)
if (show && wxGetApp().get_mode() != comSimple) {
// Show the label and the name of the STL in simple mode only.
// Label "Name: "
m_og->get_grid_sizer()->Show(size_t(0), false);
m_main_grid_sizer->Show(size_t(0), false);
// The actual name of the STL.
m_og->get_grid_sizer()->Show(size_t(1), false);
m_main_grid_sizer->Show(size_t(1), false);
}
}
@ -417,6 +431,7 @@ void ObjectManipulation::Show(const bool show)
// Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only.
bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple;
m_word_local_combo->Show(show_world_local_combo);
m_empty_str->Show(!show_world_local_combo);
}
}
@ -522,30 +537,40 @@ void ObjectManipulation::update_if_dirty()
if (label_cache != new_label_localized) {
label_cache = new_label_localized;
widget->SetLabel(new_label_localized);
if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font());
}
};
update_label(m_cache.move_label_string, m_new_move_label_string, m_move_Label);
update_label(m_cache.rotate_label_string, m_new_rotate_label_string, m_rotate_Label);
update_label(m_cache.scale_label_string, m_new_scale_label_string, m_scale_Label);
char axis[2] = "x";
for (int i = 0; i < 3; ++ i, ++ axis[0]) {
auto update = [this, i, &axis](Vec3d &cached, Vec3d &cached_rounded, const char *key, const Vec3d &new_value) {
enum ManipulationEditorKey
{
mePosition = 0,
meRotation,
meScale,
meSize
};
for (int i = 0; i < 3; ++ i) {
auto update = [this, i](Vec3d &cached, Vec3d &cached_rounded, ManipulationEditorKey key_id, const Vec3d &new_value) {
wxString new_text = double_to_string(new_value(i), 2);
double new_rounded;
new_text.ToDouble(&new_rounded);
if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
cached_rounded(i) = new_rounded;
m_og->set_value(std::string(key) + axis, new_text);
const int id = key_id*3+i;
if (id >= 0) m_editors[id]->set_value(new_text);
}
cached(i) = new_value(i);
};
update(m_cache.position, m_cache.position_rounded, "position_", m_new_position);
update(m_cache.scale, m_cache.scale_rounded, "scale_", m_new_scale);
update(m_cache.size, m_cache.size_rounded, "size_", m_new_size);
update(m_cache.rotation, m_cache.rotation_rounded, "rotation_", m_new_rotation);
update(m_cache.position, m_cache.position_rounded, mePosition, m_new_position);
update(m_cache.scale, m_cache.scale_rounded, meScale, m_new_scale);
update(m_cache.size, m_cache.size_rounded, meSize, m_new_size);
update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation);
}
if (selection.requires_uniform_scale()) {
m_lock_bnt->SetLock(true);
m_lock_bnt->SetToolTip(_(L("You cannot use non-uniform scaling mode for multiple objects/parts selection")));
@ -673,20 +698,18 @@ void ObjectManipulation::update_mirror_buttons_visibility()
#ifndef __APPLE__
void ObjectManipulation::emulate_kill_focus()
{
if (m_focused_option.empty())
if (!m_focused_editor)
return;
// we need to use a copy because the value of m_focused_option is modified inside on_change() and on_fill_empty_value()
std::string option = m_focused_option;
// see TextCtrl::propagate_value()
if (static_cast<wxTextCtrl*>(m_og->get_fieldc(option, 0)->getWindow())->GetValue().empty())
on_fill_empty_value(option);
else
on_change(option, 0);
m_focused_editor->kill_focus(this);
}
#endif // __APPLE__
void ObjectManipulation::update_item_name(const wxString& item_name)
{
m_item_name->SetLabel(item_name);
}
void ObjectManipulation::update_warning_icon_state(const wxString& tooltip)
{
m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
@ -817,76 +840,21 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale"));
}
void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value)
void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value)
{
Field* field = m_og->get_field(opt_key);
bool enter_pressed = (field != nullptr) && field->get_enter_pressed();
if (!enter_pressed)
{
// if the change does not come from the user pressing the ENTER key
// we need to hide the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
#ifndef __APPLE__
m_focused_option = "";
#endif // __APPLE__
}
else
// if the change comes from the user pressing the ENTER key, restore the key state
field->set_enter_pressed(false);
if (!m_cache.is_valid())
return;
int axis = opt_key.back() - 'x';
double new_value = boost::any_cast<double>(m_og->get_value(opt_key));
if (boost::starts_with(opt_key, "position_"))
if (opt_key == "position")
change_position_value(axis, new_value);
else if (boost::starts_with(opt_key, "rotation_"))
else if (opt_key == "rotation")
change_rotation_value(axis, new_value);
else if (boost::starts_with(opt_key, "scale_"))
else if (opt_key == "scale")
change_scale_value(axis, new_value);
else if (boost::starts_with(opt_key, "size_"))
else if (opt_key == "size")
change_size_value(axis, new_value);
}
void ObjectManipulation::on_fill_empty_value(const std::string& opt_key)
{
// needed to hide the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
#ifndef __APPLE__
m_focused_option = "";
#endif // __APPLE__
if (!m_cache.is_valid())
return;
const Vec3d *vec = nullptr;
Vec3d *rounded = nullptr;
if (boost::starts_with(opt_key, "position_")) {
vec = &m_cache.position;
rounded = &m_cache.position_rounded;
} else if (boost::starts_with(opt_key, "rotation_")) {
vec = &m_cache.rotation;
rounded = &m_cache.rotation_rounded;
} else if (boost::starts_with(opt_key, "scale_")) {
vec = &m_cache.scale;
rounded = &m_cache.scale_rounded;
} else if (boost::starts_with(opt_key, "size_")) {
vec = &m_cache.size;
rounded = &m_cache.size_rounded;
} else
assert(false);
if (vec != nullptr) {
int axis = opt_key.back() - 'x';
wxString new_text = double_to_string((*vec)(axis));
m_og->set_value(opt_key, new_text);
new_text.ToDouble(&(*rounded)(axis));
}
}
void ObjectManipulation::set_uniform_scaling(const bool new_value)
{
const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
@ -923,7 +891,10 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
void ObjectManipulation::msw_rescale()
{
const int em = wxGetApp().em_unit();
m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord));
msw_rescale_word_local_combo(m_word_local_combo);
m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1)));
m_manifold_warning_bmp.msw_rescale();
const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText();
@ -936,12 +907,120 @@ void ObjectManipulation::msw_rescale()
m_reset_scale_button->msw_rescale();
m_reset_rotation_button->msw_rescale();
m_drop_to_bed_button->msw_rescale();
m_lock_bnt->msw_rescale();
for (int id = 0; id < 3; ++id)
m_mirror_buttons[id].first->msw_rescale();
// rescale label-heights
// Text trick to grid sizer layout:
// Height of labels should be equivalent to the edit boxes
const int height = wxTextCtrl(parent(), wxID_ANY, "Br").GetBestHeight(-1);
for (wxBoxSizer* sizer : m_rescalable_sizers)
sizer->SetMinSize(wxSize(-1, height));
// rescale edit-boxes
for (ManipulationEditor* editor : m_editors)
editor->msw_rescale();
get_og()->msw_rescale();
}
static const char axes[] = { 'x', 'y', 'z' };
ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
const std::string& opt_key,
int axis) :
wxTextCtrl(parent->parent(), wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(5*int(wxGetApp().em_unit()), wxDefaultCoord), wxTE_PROCESS_ENTER),
m_opt_key(opt_key),
m_axis(axis)
{
set_font_and_background_style(this, wxGetApp().normal_font());
#ifdef __WXOSX__
this->OSXDisableAllSmartSubstitutions();
#endif // __WXOSX__
// A name used to call handle_sidebar_focus_event()
m_full_opt_name = m_opt_key+"_"+axes[axis];
// Reset m_enter_pressed flag to _false_, when value is editing
this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
this->Bind(wxEVT_TEXT_ENTER, [this, parent](wxEvent&)
{
m_enter_pressed = true;
parent->on_change(m_opt_key, m_axis, get_value());
}, this->GetId());
this->Bind(wxEVT_KILL_FOCUS, [this, parent](wxFocusEvent& e)
{
parent->set_focused_editor(nullptr);
if (!m_enter_pressed)
kill_focus(parent);
e.Skip();
}, this->GetId());
this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e)
{
parent->set_focused_editor(this);
// needed to show the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, true);
e.Skip();
}, this->GetId());
this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event)
{
// select all text using Ctrl+A
if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
this->SetSelection(-1, -1); //select all
event.Skip();
}));
}
void ManipulationEditor::msw_rescale()
{
const int em = wxGetApp().em_unit();
SetMinSize(wxSize(5 * em, wxDefaultCoord));
}
double ManipulationEditor::get_value()
{
wxString str = GetValue();
double value;
// Replace the first occurence of comma in decimal number.
str.Replace(",", ".", false);
if (str == ".")
value = 0.0;
if ((str.IsEmpty() || !str.ToCDouble(&value)) && !m_valid_value.IsEmpty()) {
str = m_valid_value;
SetValue(str);
str.ToCDouble(&value);
}
return value;
}
void ManipulationEditor::set_value(const wxString& new_value)
{
if (new_value.IsEmpty())
return;
m_valid_value = new_value;
SetValue(m_valid_value);
}
void ManipulationEditor::kill_focus(ObjectManipulation* parent)
{
parent->on_change(m_opt_key, m_axis, get_value());
// if the change does not come from the user pressing the ENTER key
// we need to hide the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, false);
}
} //namespace GUI
} //namespace Slic3r

View File

@ -16,6 +16,29 @@ namespace GUI {
class Selection;
class ObjectManipulation;
class ManipulationEditor : public wxTextCtrl
{
std::string m_opt_key;
int m_axis;
bool m_enter_pressed { false };
wxString m_valid_value {wxEmptyString};
std::string m_full_opt_name;
public:
ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, int axis);
~ManipulationEditor() {}
void msw_rescale();
void set_value(const wxString& new_value);
void kill_focus(ObjectManipulation *parent);
private:
double get_value();
};
class ObjectManipulation : public OG_Settings
{
struct Cache
@ -53,6 +76,9 @@ class ObjectManipulation : public OG_Settings
wxStaticText* m_scale_Label = nullptr;
wxStaticText* m_rotate_Label = nullptr;
wxStaticText* m_item_name = nullptr;
wxStaticText* m_empty_str = nullptr;
// Non-owning pointers to the reset buttons, so we can hide and show them.
ScalableButton* m_reset_scale_button = nullptr;
ScalableButton* m_reset_rotation_button = nullptr;
@ -81,7 +107,7 @@ class ObjectManipulation : public OG_Settings
Vec3d m_new_rotation;
Vec3d m_new_scale;
Vec3d m_new_size;
bool m_new_enabled;
bool m_new_enabled {true};
bool m_uniform_scale {true};
// Does the object manipulation panel work in World or Local coordinates?
bool m_world_coordinates = true;
@ -92,10 +118,19 @@ class ObjectManipulation : public OG_Settings
wxStaticBitmap* m_fix_throught_netfab_bitmap;
#ifndef __APPLE__
// Currently focused option name (empty if none)
std::string m_focused_option;
// Currently focused editor (nullptr if none)
ManipulationEditor* m_focused_editor {nullptr};
#endif // __APPLE__
wxFlexGridSizer* m_main_grid_sizer;
wxFlexGridSizer* m_labels_grid_sizer;
// sizers, used for msw_rescale
wxBoxSizer* m_word_local_combo_sizer;
std::vector<wxBoxSizer*> m_rescalable_sizers;
std::vector<ManipulationEditor*> m_editors;
public:
ObjectManipulation(wxWindow* parent);
~ObjectManipulation() {}
@ -122,8 +157,15 @@ public:
void emulate_kill_focus();
#endif // __APPLE__
void update_item_name(const wxString &item_name);
void update_warning_icon_state(const wxString& tooltip);
void msw_rescale();
void on_change(const std::string& opt_key, int axis, double new_value);
void set_focused_editor(ManipulationEditor* focused_editor) {
#ifndef __APPLE__
m_focused_editor = focused_editor;
#endif // __APPLE__
}
private:
void reset_settings_value();
@ -140,9 +182,6 @@ private:
void change_scale_value(int axis, double value);
void change_size_value(int axis, double value);
void do_scale(int axis, const Vec3d &scale) const;
void on_change(t_config_option_key opt_key, const boost::any& value);
void on_fill_empty_value(const std::string& opt_key);
};
}}

View File

@ -417,6 +417,9 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
{
// used to set a right up event as processed when needed
static bool pending_right_up = false;
Point pos(evt.GetX(), evt.GetY());
Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY());
@ -442,7 +445,14 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
else if (evt.MiddleUp())
m_mouse_capture.middle = false;
else if (evt.RightUp())
{
m_mouse_capture.right = false;
if (pending_right_up)
{
pending_right_up = false;
processed = true;
}
}
else if (evt.Dragging() && m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
@ -473,8 +483,12 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
}
}
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown))
{
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
pending_right_up = true;
// event was taken care of by the SlaSupports gizmo
processed = true;
}
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
// don't allow dragging objects with the Sla gizmo on
processed = true;

View File

@ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const
const PrintObjects& objects = m_plater->sla_print().objects();
for (const SLAPrintObject* object : objects)
{
if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree))
if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree))
{
can_export = true;
break;

View File

@ -10,6 +10,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
@ -251,11 +252,18 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 *
auto selected_item = this->GetSelection();
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);
evt.StopPropagation();
if (marker == LABEL_ITEM_CONFIG_WIZARD)
wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); });
if (marker >= LABEL_ITEM_WIZARD_PRINTERS) {
ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME;
switch (marker) {
case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break;
case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break;
case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
}
wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); });
}
} else if ( this->last_selected != selected_item ||
wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) {
this->last_selected = selected_item;
@ -521,12 +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_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().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];
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
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
for (auto obj : model.objects) count += obj->instances.size();
m_selected.clear(), m_unselected.clear();
m_selected.clear();
m_unselected.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
}
@ -1590,11 +1594,12 @@ struct Plater::priv
// Set up arrange polygon for a ModelInstance and Wipe tower
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
ArrangePolygon ap = obj->get_arrange_polygon();
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
if (p.is_arranged()) {
auto t = p.translation; t.x() += p.bed_idx * bed_stride();
auto t = p.translation;
t.x() += p.bed_idx * bed_stride();
obj->apply_arrange_result(t, p.rotation);
}
};
@ -1625,7 +1630,8 @@ struct Plater::priv
obj_sel(model.objects.size(), nullptr);
for (auto &s : plater().get_selection().get_content())
if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second;
if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = &s.second;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
@ -1635,7 +1641,8 @@ struct Plater::priv
std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist)
for (auto inst_id : *instlist) inst_sel[inst_id] = true;
for (auto inst_id : *instlist)
inst_sel[size_t(inst_id)] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
@ -1907,6 +1914,7 @@ struct Plater::priv
bool can_fix_through_netfabb() const;
bool can_set_instance_to_object() const;
bool can_mirror() const;
bool can_reload_from_disk() const;
void msw_rescale_object_menu();
@ -1943,7 +1951,6 @@ private:
* */
std::string m_last_fff_printer_profile_name;
std::string m_last_sla_printer_profile_name;
bool m_update_objects_list_on_loading{ true };
};
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@ -2469,11 +2476,8 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
_(L("Object too large?")));
}
if (m_update_objects_list_on_loading)
{
for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx);
}
for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx);
}
update();
@ -2764,9 +2768,8 @@ void Plater::priv::ArrangeJob::process() {
try {
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
[this, count](unsigned st) {
if (st >
0) // will not finalize after last one
update_status(count - st, arrangestr);
if (st > 0) // will not finalize after last one
update_status(int(count - st), arrangestr);
},
[this]() { return was_canceled(); });
} catch (std::exception & /*e*/) {
@ -3098,88 +3101,110 @@ void Plater::priv::update_sla_scene()
void Plater::priv::reload_from_disk()
{
Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk")));
Plater::TakeSnapshot snapshot(q, _(L("Reload from disk")));
auto& selection = get_selection();
const auto obj_orig_idx = selection.get_object_idx();
if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; }
int instance_idx = selection.get_instance_idx();
const Selection& selection = get_selection();
auto *object_orig = model.objects[obj_orig_idx];
std::vector<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);
if (selection.is_wipe_tower())
return;
}
for (const auto idx : new_idxs)
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
ModelObject *object = model.objects[idx];
object->config.apply(object_orig->config);
int object_idx;
int volume_idx;
object->clear_instances();
for (const ModelInstance *instance : object_orig->instances)
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
};
std::vector<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);
}
for (const ModelVolume* v : object_orig->volumes)
{
if (v->is_modifier())
object->add_volume(*v);
}
Vec3d offset = object_orig->origin_translation - object->origin_translation;
if (object->volumes.size() == object_orig->volumes.size())
{
for (size_t i = 0; i < object->volumes.size(); i++)
new_model = Model::read_from_file(path, nullptr, true, false);
for (ModelObject* model_object : new_model.objects)
{
object->volumes[i]->config.apply(object_orig->volumes[i]->config);
object->volumes[i]->translate(offset);
model_object->center_around_origin();
model_object->ensure_on_bed();
}
}
catch (std::exception&)
{
// error while loading
view3D->get_canvas3d()->enable_render(true);
return;
}
// XXX: Restore more: layer_height_ranges, layer_height_profile (?)
// update the selected volumes whose source is the current file
for (const SelectedVolume& old_v : selected_volumes)
{
ModelObject* old_model_object = model.objects[old_v.object_idx];
ModelVolume* old_volume = old_model_object->volumes[old_v.volume_idx];
int new_volume_idx = old_volume->source.volume_idx;
int new_object_idx = old_volume->source.object_idx;
if (old_volume->source.input_file == path)
{
if (new_object_idx < (int)new_model.objects.size())
{
ModelObject* new_model_object = new_model.objects[new_object_idx];
if (new_volume_idx < (int)new_model_object->volumes.size())
{
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
ModelVolume* new_volume = old_model_object->volumes.back();
new_volume->set_new_unique_id();
new_volume->config.apply(old_volume->config);
new_volume->set_type(old_volume->type());
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(old_volume->get_transformation());
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
}
}
}
}
}
// re-enable update of objects list
m_update_objects_list_on_loading = true;
model.adjust_min_z();
// puts the new objects into the list
for (const auto idx : new_idxs)
{
wxGetApp().obj_list()->add_object_to_list(idx);
}
remove(obj_orig_idx);
// update 3D scene
update();
// new GLVolumes have been created at this point, so update their printable state
for (size_t i = 0; i < model.objects.size(); ++i)
{
view3D->get_canvas3d()->update_instance_printable_state_for_object(i);
}
// re-enable render
view3D->get_canvas3d()->enable_render(true);
// the previous call to remove() clears the selection
// select newly added objects
selection.clear();
for (const auto idx : new_idxs)
{
selection.add_instance((unsigned int)idx - 1, instance_idx, false);
}
}
void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
@ -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")),
[this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q);
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")),
[this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q);
sidebar->obj_list()->append_menu_item_export_stl(menu);
}
else {
@ -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);
menu->AppendSeparator();
append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")),
[this](wxCommandEvent&) { reload_from_disk(); });
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected object from disk")),
[this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q);
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
[this](wxCommandEvent&) { q->export_stl(false, true); });
@ -3786,6 +3814,48 @@ bool Plater::priv::can_mirror() const
return get_selection().is_from_single_instance();
}
bool Plater::priv::can_reload_from_disk() const
{
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
int object_idx;
int volume_idx;
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
};
std::vector<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)
{
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();
TriangleMesh pad_mesh;
bool has_pad_mesh = object->has_mesh(slaposBasePool);
bool has_pad_mesh = object->has_mesh(slaposPad);
if (has_pad_mesh)
{
pad_mesh = object->get_mesh(slaposBasePool);
pad_mesh = object->get_mesh(slaposPad);
pad_mesh.transform(mesh_trafo_inv);
}
@ -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
{
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.
if (!this->p->background_processing_enabled()) {
task.single_model_instance_only = true;
task.to_object_step = slaposBasePool;
task.to_object_step = slaposPad;
}
this->p->background_process.set_task(task);
// and let the background processing start.
@ -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));
p->config->option<ConfigOptionStrings>(opt_key)->values = filament_colors;
p->sidebar->obj_list()->update_extruder_colors();
continue;
}
}
@ -4815,6 +4891,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
else if(opt_key == "extruder_colour") {
update_scheduled = true;
p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
p->sidebar->obj_list()->update_extruder_colors();
} else if(opt_key == "max_print_height") {
update_scheduled = true;
}
@ -4863,8 +4940,10 @@ void Plater::force_filament_colors_update()
}
}
if (update_scheduled)
if (update_scheduled) {
update();
p->sidebar->obj_list()->update_extruder_colors();
}
if (p->main_frame->is_loaded())
this->p->schedule_background_process();
@ -4891,6 +4970,22 @@ const DynamicPrintConfig* Plater::get_plater_config() const
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
{
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_redo() const { return p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }

View File

@ -56,8 +56,12 @@ public:
ScalableButton* edit_btn { nullptr };
enum LabelItemType {
LABEL_ITEM_MARKER = 0x4d,
LABEL_ITEM_CONFIG_WIZARD = 0x4e
LABEL_ITEM_MARKER = 0xffffff01,
LABEL_ITEM_WIZARD_PRINTERS,
LABEL_ITEM_WIZARD_FILAMENTS,
LABEL_ITEM_WIZARD_MATERIALS,
LABEL_ITEM_MAX,
};
void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER);
@ -183,6 +187,7 @@ public:
void export_stl(bool extended = false, bool selection_only = false);
void export_amf();
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reload_from_disk();
bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj() const;
void reslice();
@ -215,6 +220,7 @@ public:
// On activating the parent window.
void on_activate();
const DynamicPrintConfig* get_plater_config() const;
std::vector<std::string> get_extruder_colors_from_plater_config() const;
void update_object_menu();
@ -248,6 +254,7 @@ public:
bool can_copy_to_clipboard() const;
bool can_undo() const;
bool can_redo() const;
bool can_reload_from_disk() const;
void msw_rescale();

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