mirror of
https://github.com/FULU-Foundation/OrcaSlicer-bambulab.git
synced 2026-07-10 07:54:26 +00:00
Initial release
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(OrcaSlicer-native)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Include dev-utils for encoding check and other utilities
|
||||
add_subdirectory(dev-utils)
|
||||
# add_subdirectory(avrdude)
|
||||
# Note: semver and hints are now included from deps_src/CMakeLists.txt
|
||||
|
||||
find_package(libnoise REQUIRED)
|
||||
|
||||
add_subdirectory(libslic3r)
|
||||
|
||||
if (SLIC3R_GUI)
|
||||
add_subdirectory(glad)
|
||||
# imgui, imguizmo, and hidapi are now included from deps_src
|
||||
add_subdirectory(libvgcode)
|
||||
|
||||
if(WIN32)
|
||||
message(STATUS "WXWIN environment set to: $ENV{WXWIN}")
|
||||
elseif(UNIX)
|
||||
set(wxWidgets_USE_UNICODE ON)
|
||||
if(SLIC3R_STATIC)
|
||||
set(wxWidgets_USE_STATIC ON)
|
||||
else()
|
||||
set(wxWidgets_USE_STATIC OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}")
|
||||
find_package(wxWidgets 3.3 REQUIRED COMPONENTS base core adv html gl aui net media webview)
|
||||
else ()
|
||||
find_package(wxWidgets 3.3 CONFIG REQUIRED COMPONENTS html adv gl core base webview aui net media)
|
||||
endif ()
|
||||
|
||||
if(UNIX)
|
||||
message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}")
|
||||
endif()
|
||||
|
||||
if(wxWidgets_USE_FILE)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
endif()
|
||||
|
||||
find_package(JPEG QUIET)
|
||||
|
||||
string(REGEX MATCH "wxpng" WX_PNG_BUILTIN ${wxWidgets_LIBRARIES})
|
||||
if (PNG_FOUND AND NOT WX_PNG_BUILTIN)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX png)
|
||||
list(APPEND wxWidgets_LIBRARIES ${PNG_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
string(REGEX MATCH "wxjpeg" WX_JPEG_BUILTIN ${wxWidgets_LIBRARIES})
|
||||
if (JPEG_FOUND AND NOT WX_JPEG_BUILTIN)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX jpeg)
|
||||
list(APPEND wxWidgets_LIBRARIES ${JPEG_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
string(REGEX MATCH "wxexpat" WX_EXPAT_BUILTIN ${wxWidgets_LIBRARIES})
|
||||
if (EXPAT_FOUND AND NOT WX_EXPAT_BUILTIN)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat)
|
||||
list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
# This is an issue in the new wxWidgets cmake build, doesn't deal with librt
|
||||
find_library(LIBRT rt)
|
||||
if(LIBRT)
|
||||
list(APPEND wxWidgets_LIBRARIES ${LIBRT})
|
||||
endif()
|
||||
|
||||
# This fixes a OpenGL linking issue on OSX. wxWidgets cmake build includes
|
||||
# wrong libs for opengl in the link line and it does not link to it by himself.
|
||||
# libslic3r_gui will link to opengl anyway, so lets override wx
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL)
|
||||
|
||||
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
||||
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
|
||||
|
||||
add_subdirectory(slic3r)
|
||||
endif()
|
||||
|
||||
if(ORCA_TOOLS)
|
||||
# OrcaSlicer_profile_validator
|
||||
if(APPLE)
|
||||
add_executable(OrcaSlicer_profile_validator MACOSX_BUNDLE dev-utils/OrcaSlicer_profile_validator.cpp)
|
||||
set_target_properties(OrcaSlicer_profile_validator PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "OrcaSlicer Profile Validator"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${SLIC3R_VERSION}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${SLIC3R_VERSION}"
|
||||
MACOSX_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer.profile-validator"
|
||||
MACOSX_BUNDLE_COPYRIGHT "© 2026 OrcaSlicer Pte Ltd All Rights Reserved"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "com.orcaslicer.OrcaSlicer.profile-validator"
|
||||
)
|
||||
else()
|
||||
add_executable(OrcaSlicer_profile_validator dev-utils/OrcaSlicer_profile_validator.cpp)
|
||||
endif()
|
||||
target_link_libraries(OrcaSlicer_profile_validator libslic3r boost_headeronly libcurl OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(OrcaSlicer_profile_validator PRIVATE -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8)
|
||||
endif()
|
||||
|
||||
# Create a slic3r executable
|
||||
# Process manifests for various platforms.
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.rc @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.manifest @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
|
||||
if (WIN32)
|
||||
add_library(OrcaSlicer SHARED OrcaSlicer.cpp OrcaSlicer.hpp)
|
||||
else ()
|
||||
add_executable(OrcaSlicer OrcaSlicer.cpp OrcaSlicer.hpp)
|
||||
endif ()
|
||||
|
||||
if (MINGW)
|
||||
target_link_options(OrcaSlicer PUBLIC "-Wl,-allow-multiple-definition")
|
||||
set_target_properties(OrcaSlicer PROPERTIES PREFIX "")
|
||||
endif (MINGW)
|
||||
|
||||
if (NOT WIN32 AND NOT APPLE)
|
||||
# Binary name on unix like systems (Linux, Unix)
|
||||
set_target_properties(OrcaSlicer PROPERTIES OUTPUT_NAME "orca-slicer")
|
||||
set(SLIC3R_APP_CMD "orca-slicer")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/unix/build_linux_image.sh.in ${CMAKE_CURRENT_BINARY_DIR}/build_linux_image.sh USE_SOURCE_PERMISSIONS @ONLY)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(OrcaSlicer libslic3r cereal::cereal)
|
||||
|
||||
set(PJARCZAK_WINDOWS_RUNTIME_SUPPORT_FILES "")
|
||||
set(PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES "")
|
||||
if (WIN32)
|
||||
foreach(_runtime_support_file
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/wsl/pjarczak_wsl_distro.txt"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/wsl/install_runtime.ps1"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/wsl/install_runtime.cmd"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/wsl/verify_runtime.ps1"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/wsl/pjarczak_wsl_run_host.sh"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/wsl/pjarczak_plugin_cache_subdir.txt"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/release/assemble_windows_runtime_bundle.ps1")
|
||||
if (EXISTS "${_runtime_support_file}")
|
||||
file(TO_CMAKE_PATH "${_runtime_support_file}" _runtime_support_file_cmake)
|
||||
list(APPEND PJARCZAK_WINDOWS_RUNTIME_SUPPORT_FILES "${_runtime_support_file_cmake}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if (DEFINED PJARCZAK_WSL_RUNTIME_DIR AND NOT "${PJARCZAK_WSL_RUNTIME_DIR}" STREQUAL "")
|
||||
if (NOT EXISTS "${PJARCZAK_WSL_RUNTIME_DIR}")
|
||||
message(FATAL_ERROR "PJARCZAK_WSL_RUNTIME_DIR does not exist: ${PJARCZAK_WSL_RUNTIME_DIR}")
|
||||
endif()
|
||||
|
||||
file(GLOB PJARCZAK_WSL_RUNTIME_DIR_GLOB LIST_DIRECTORIES false "${PJARCZAK_WSL_RUNTIME_DIR}/*")
|
||||
if (NOT PJARCZAK_WSL_RUNTIME_DIR_GLOB)
|
||||
message(FATAL_ERROR "PJARCZAK_WSL_RUNTIME_DIR is empty: ${PJARCZAK_WSL_RUNTIME_DIR}")
|
||||
endif()
|
||||
|
||||
foreach(_runtime_file ${PJARCZAK_WSL_RUNTIME_DIR_GLOB})
|
||||
if (EXISTS "${_runtime_file}" AND NOT IS_DIRECTORY "${_runtime_file}")
|
||||
file(TO_CMAKE_PATH "${_runtime_file}" _runtime_file_cmake)
|
||||
list(APPEND PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES "${_runtime_file_cmake}")
|
||||
endif()
|
||||
endforeach()
|
||||
else()
|
||||
foreach(_runtime_file
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/pjarczak_bambu_linux_host"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/pjarczak_bambu_linux_host_abi1"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/pjarczak_bambu_linux_host_abi0"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/libbambu_networking.so"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/libBambuSource.so"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/linux_payload_manifest.json"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/libcrypto.so.3"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/libz.so.1"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64/libzstd.so.1"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/rootfs/windows-wsl2-rootfs.tar"
|
||||
"${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/windows-wsl2-rootfs.tar")
|
||||
if (EXISTS "${_runtime_file}")
|
||||
file(TO_CMAKE_PATH "${_runtime_file}" _runtime_file_cmake)
|
||||
list(APPEND PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES "${_runtime_file_cmake}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(_cert_candidate
|
||||
"${CMAKE_SOURCE_DIR}/cert/ca-certificates.crt"
|
||||
"${CMAKE_SOURCE_DIR}/resources/cert/ca-certificates.crt")
|
||||
if (EXISTS "${_cert_candidate}")
|
||||
file(TO_CMAKE_PATH "${_cert_candidate}" _runtime_file_cmake)
|
||||
list(APPEND PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES "${_runtime_file_cmake}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(_cert_candidate
|
||||
"${CMAKE_SOURCE_DIR}/cert/slicer_base64.cer"
|
||||
"${CMAKE_SOURCE_DIR}/resources/cert/slicer_base64.cer")
|
||||
if (EXISTS "${_cert_candidate}")
|
||||
file(TO_CMAKE_PATH "${_cert_candidate}" _runtime_file_cmake)
|
||||
list(APPEND PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES "${_runtime_file_cmake}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# target_include_directories(OrcaSlicer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/slic3r)
|
||||
if (APPLE)
|
||||
# add_compile_options(-stdlib=libc++)
|
||||
# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
|
||||
# -liconv: boost links to libiconv by default
|
||||
target_link_libraries(OrcaSlicer "-liconv -framework IOKit" "-framework CoreFoundation" "-framework AVFoundation" "-framework AVKit" "-framework CoreMedia" "-framework VideoToolbox" -lc++)
|
||||
elseif (MSVC)
|
||||
# Manifest is provided through OrcaSlicer.rc, don't generate your own.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
|
||||
else ()
|
||||
# Boost on Raspberry-Pi does not link to pthreads explicitly.
|
||||
target_link_libraries(OrcaSlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads pangoft2-1.0)
|
||||
endif ()
|
||||
|
||||
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
|
||||
if (SLIC3R_GUI)
|
||||
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
|
||||
target_link_libraries(OrcaSlicer libslic3r_gui)
|
||||
if (MSVC)
|
||||
# Generate debug symbols even in release mode.
|
||||
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||
target_link_libraries(OrcaSlicer user32.lib Setupapi.lib)
|
||||
elseif (MINGW)
|
||||
target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi)
|
||||
elseif (APPLE)
|
||||
target_link_libraries(OrcaSlicer "-framework OpenGL")
|
||||
else ()
|
||||
target_link_libraries(OrcaSlicer -ldl)
|
||||
endif ()
|
||||
#if (WIN32)
|
||||
# find_library(PSAPI_LIB NAMES Psapi)
|
||||
# target_link_libraries(OrcaSlicer ${PSAPI_LIB})
|
||||
#endif ()
|
||||
endif ()
|
||||
|
||||
# On Windows, a shim application is required to produce a console / non console version of the Slic3r application.
|
||||
# Also the shim may load the Mesa software OpenGL renderer if the default renderer does not support OpenGL 2.0 and higher.
|
||||
if (WIN32)
|
||||
if (MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
|
||||
endif()
|
||||
|
||||
add_executable(OrcaSlicer_app_gui WIN32 OrcaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.rc)
|
||||
# Generate debug symbols even in release mode.
|
||||
if(MSVC)
|
||||
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||
endif()
|
||||
target_compile_definitions(OrcaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE)
|
||||
add_dependencies(OrcaSlicer_app_gui OrcaSlicer)
|
||||
set_target_properties(OrcaSlicer_app_gui PROPERTIES OUTPUT_NAME "orca-slicer")
|
||||
target_link_libraries(OrcaSlicer_app_gui PRIVATE boost_headeronly)
|
||||
endif ()
|
||||
|
||||
# Link the resources dir to where Slic3r GUI expects it
|
||||
set(output_dlls_Release "")
|
||||
set(output_dlls_Debug "")
|
||||
set(output_dlls_RelWithDebInfo "")
|
||||
if (WIN32)
|
||||
# This has to be a separate target due to the windows command line length limits
|
||||
add_custom_target(COPY_DLLS ALL DEPENDS OrcaSlicer)
|
||||
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
foreach (CONF ${CMAKE_CONFIGURATION_TYPES})
|
||||
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CONF}" WIN_CONF_OUTPUT_DIR)
|
||||
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CONF}/resources" WIN_RESOURCES_SYMLINK)
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND if exist "${WIN_CONF_OUTPUT_DIR}" "("
|
||||
if not exist "${WIN_RESOURCES_SYMLINK}" "("
|
||||
mklink /J "${WIN_RESOURCES_SYMLINK}" "${SLIC3R_RESOURCES_DIR_WIN}"
|
||||
")"
|
||||
")"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM
|
||||
)
|
||||
endforeach ()
|
||||
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
|
||||
elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||
orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
|
||||
else()
|
||||
orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
|
||||
endif()
|
||||
else ()
|
||||
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources" WIN_RESOURCES_SYMLINK)
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND if not exist "${WIN_RESOURCES_SYMLINK}" "(" mklink /J "${WIN_RESOURCES_SYMLINK}" "${SLIC3R_RESOURCES_DIR_WIN}" ")"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
||||
else ()
|
||||
if (NOT APPLE AND NOT FLATPAK)
|
||||
set(output_sos_Release "")
|
||||
set(output_sos_Debug "")
|
||||
add_custom_target(OrcaSlicerSosCopy ALL DEPENDS OrcaSlicer)
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
orcaslicer_copy_sos(OrcaSlicerSosCopy "Debug" "d" output_sos_Debug)
|
||||
else()
|
||||
orcaslicer_copy_sos(OrcaSlicerSosCopy "Release" "" output_sos_Release)
|
||||
endif()
|
||||
endif()
|
||||
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
|
||||
# On OSX, the name of the binary matches the name of the Application.
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ln -sf OrcaSlicer orca-slicer
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
|
||||
VERBATIM)
|
||||
else ()
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
|
||||
VERBATIM)
|
||||
endif ()
|
||||
if (XCODE)
|
||||
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
|
||||
set(BIN_CONF_DIR "Debug")
|
||||
else ()
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources")
|
||||
endif ()
|
||||
if (CMAKE_MACOSX_BUNDLE)
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/OrcaSlicer.app/Contents/Resources")
|
||||
else()
|
||||
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.app/Contents/Resources")
|
||||
endif()
|
||||
set(MACOSX_BUNDLE_ICON_FILE Icon.icns)
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "OrcaSlicer")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${SoftFever_VERSION})
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright(C) 2022-2024 Li Jiang All Rights Reserved")
|
||||
endif()
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM)
|
||||
endif ()
|
||||
|
||||
# Slic3r binary install target. Default build type is release in case no CMAKE_BUILD_TYPE is provided.
|
||||
if( ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") OR ("${CMAKE_BUILD_TYPE}" STREQUAL "") )
|
||||
set (build_type "Release")
|
||||
set(CMAKE_BUILD_POSTFIX "")
|
||||
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||
set (build_type "RelWithDebInfo")
|
||||
set(CMAKE_BUILD_POSTFIX "")
|
||||
else()
|
||||
set (build_type "Debug")
|
||||
set(CMAKE_BUILD_POSTFIX "d")
|
||||
endif()
|
||||
message(STATUS "libslic3r-CMAKE_BUILD_TYPE: ${build_type}")
|
||||
message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
|
||||
if (WIN32)
|
||||
install(TARGETS OrcaSlicer RUNTIME DESTINATION ".")
|
||||
if (MSVC)
|
||||
install(TARGETS OrcaSlicer_app_gui RUNTIME DESTINATION ".")
|
||||
endif ()
|
||||
if (TARGET pjarczak_bambu_networking_bridge)
|
||||
install(TARGETS pjarczak_bambu_networking_bridge RUNTIME DESTINATION ".")
|
||||
endif ()
|
||||
if (PJARCZAK_WINDOWS_RUNTIME_SUPPORT_FILES)
|
||||
install(FILES ${PJARCZAK_WINDOWS_RUNTIME_SUPPORT_FILES} DESTINATION ".")
|
||||
endif()
|
||||
if (PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES)
|
||||
install(FILES ${PJARCZAK_OPTIONAL_WINDOWS_RUNTIME_FILES} DESTINATION ".")
|
||||
endif()
|
||||
install(FILES ${output_dlls_${build_type}} DESTINATION ".")
|
||||
else ()
|
||||
if (APPLE)
|
||||
else()
|
||||
install(FILES ${output_sos_${build_type}} DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif ()
|
||||
+7442
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,142 @@
|
||||
#ifndef SLIC3R_HPP
|
||||
#define SLIC3R_HPP
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace IO {
|
||||
enum ExportFormat : int {
|
||||
AMF,
|
||||
OBJ,
|
||||
STL,
|
||||
// SVG,
|
||||
TMF,
|
||||
Gcode
|
||||
};
|
||||
}
|
||||
|
||||
#define JSON_ASSEMPLE_PLATES "plates"
|
||||
#define JSON_ASSEMPLE_PLATE_PARAMS "plate_params"
|
||||
#define JSON_ASSEMPLE_PLATE_NAME "plate_name"
|
||||
#define JSON_ASSEMPLE_PLATE_NEED_ARRANGE "need_arrange"
|
||||
#define JSON_ASSEMPLE_OBJECTS "objects"
|
||||
#define JSON_ASSEMPLE_OBJECT_PATH "path"
|
||||
#define JSON_ASSEMPLE_OBJECT_COUNT "count"
|
||||
#define JSON_ASSEMPLE_OBJECT_FILAMENTS "filaments"
|
||||
#define JSON_ASSEMPLE_OBJECT_POS_X "pos_x"
|
||||
#define JSON_ASSEMPLE_OBJECT_POS_Y "pos_y"
|
||||
#define JSON_ASSEMPLE_OBJECT_POS_Z "pos_z"
|
||||
#define JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX "assemble_index"
|
||||
#define JSON_ASSEMPLE_OBJECT_PRINT_PARAMS "print_params"
|
||||
#define JSON_ASSEMPLE_ASSEMBLE_PARAMS "assembled_params"
|
||||
|
||||
|
||||
#define JSON_ASSEMPLE_OBJECT_MIN_Z "min_z"
|
||||
#define JSON_ASSEMPLE_OBJECT_MAX_Z "max_z"
|
||||
#define JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES "height_ranges"
|
||||
#define JSON_ASSEMPLE_OBJECT_RANGE_PARAMS "range_params"
|
||||
|
||||
typedef struct _height_range_info {
|
||||
float min_z;
|
||||
float max_z;
|
||||
|
||||
std::map<std::string, std::string> range_params;
|
||||
}height_range_info_t;
|
||||
|
||||
typedef struct _assembled_param_info {
|
||||
std::map<std::string, std::string> print_params;
|
||||
std::vector<height_range_info_t> height_ranges;
|
||||
}assembled_param_info_t;
|
||||
|
||||
typedef struct _assemble_object_info {
|
||||
std::string path;
|
||||
int count;
|
||||
|
||||
std::vector<int> filaments;
|
||||
std::vector<int> assemble_index;
|
||||
std::vector<float> pos_x;
|
||||
std::vector<float> pos_y;
|
||||
std::vector<float> pos_z;
|
||||
std::map<std::string, std::string> print_params;
|
||||
std::vector<height_range_info_t> height_ranges;
|
||||
}assemble_object_info_t;
|
||||
|
||||
typedef struct _assemble_plate_info {
|
||||
std::string plate_name;
|
||||
bool need_arrange {false};
|
||||
int filaments_count {0};
|
||||
|
||||
std::map<std::string, std::string> plate_params;
|
||||
std::vector<assemble_object_info_t> assemble_obj_list;
|
||||
std::vector<ModelObject *> loaded_obj_list;
|
||||
std::map<int, assembled_param_info_t> assembled_param_list;
|
||||
}assemble_plate_info_t;
|
||||
|
||||
|
||||
typedef struct _printer_plate_info {
|
||||
std::string printer_name;
|
||||
int printable_width{0};
|
||||
int printable_depth{0};
|
||||
int printable_height{0};
|
||||
|
||||
int exclude_width{0};
|
||||
int exclude_depth{0};
|
||||
int exclude_x{0};
|
||||
int exclude_y{0};
|
||||
|
||||
int wrapping_width{0};
|
||||
int wrapping_depth{0};
|
||||
int wrapping_x{0};
|
||||
int wrapping_y{0};
|
||||
}printer_plate_info_t;
|
||||
|
||||
typedef struct _plate_obj_size_info {
|
||||
bool has_wipe_tower{false};
|
||||
float wipe_x{0.f};
|
||||
float wipe_y{0.f};
|
||||
float wipe_width{0.f};
|
||||
float wipe_depth{0.f};
|
||||
BoundingBoxf3 obj_bbox;
|
||||
}plate_obj_size_info_t;
|
||||
|
||||
|
||||
class CLI {
|
||||
public:
|
||||
int run(int argc, char **argv);
|
||||
|
||||
private:
|
||||
DynamicPrintAndCLIConfig m_config;
|
||||
DynamicPrintConfig m_print_config;
|
||||
DynamicPrintConfig m_extra_config;
|
||||
std::vector<std::string> m_input_files;
|
||||
std::vector<std::string> m_actions;
|
||||
std::vector<std::string> m_transforms;
|
||||
std::vector<Model> m_models;
|
||||
|
||||
bool setup(int argc, char **argv);
|
||||
|
||||
/// Prints usage of the CLI.
|
||||
void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny) const;
|
||||
|
||||
/// Exports loaded models to a file of the specified format, according to the options affecting output filename.
|
||||
bool export_models(IO::ExportFormat format, std::string path = std::string());
|
||||
//BBS: add export_project function
|
||||
bool export_project(Model *model, std::string& path, PlateDataPtrs &partplate_data, std::vector<Preset*>& project_presets,
|
||||
std::vector<ThumbnailData *> &thumbnails,
|
||||
std::vector<ThumbnailData *> &no_light_thumbnails,
|
||||
std::vector<ThumbnailData *> &top_thumbnails,
|
||||
std::vector<ThumbnailData *> &pick_thumbnails,
|
||||
std::vector<ThumbnailData*>& calibration_thumbnails,
|
||||
std::vector<PlateBBoxData*>& plate_bboxes, const DynamicPrintConfig* config, bool minimum_save, int plate_to_export = -1);
|
||||
|
||||
bool has_print_action() const { return m_config.opt_bool("export_gcode") || m_config.opt_bool("export_sla"); }
|
||||
|
||||
std::string output_filepath(const Model &model, IO::ExportFormat format) const;
|
||||
std::string output_filepath(const ModelObject &object, unsigned int index, IO::ExportFormat format, std::string path_dir) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,310 @@
|
||||
// Why?
|
||||
#define _WIN32_WINNT 0x0502
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <wchar.h>
|
||||
|
||||
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
extern "C"
|
||||
{
|
||||
// Let the NVIDIA and AMD know we want to use their graphics card
|
||||
// on a dual graphics card system.
|
||||
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
||||
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
}
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
#include <GL/GL.h>
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
class OpenGLVersionCheck
|
||||
{
|
||||
public:
|
||||
std::string version;
|
||||
std::string glsl_version;
|
||||
std::string vendor;
|
||||
std::string renderer;
|
||||
|
||||
HINSTANCE hOpenGL = nullptr;
|
||||
bool success = false;
|
||||
|
||||
bool load_opengl_dll()
|
||||
{
|
||||
MSG msg = {0};
|
||||
WNDCLASS wc = {0};
|
||||
wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc;
|
||||
wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr);
|
||||
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
|
||||
wc.lpszClassName = L"OrcaSlicer_opengl_version_check";
|
||||
wc.style = CS_OWNDC;
|
||||
if (RegisterClass(&wc)) {
|
||||
HWND hwnd = CreateWindowW(wc.lpszClassName, L"OrcaSlicer_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this);
|
||||
if (hwnd) {
|
||||
message_pump_exit = false;
|
||||
while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit)
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
return this->success;
|
||||
}
|
||||
|
||||
void unload_opengl_dll()
|
||||
{
|
||||
if (this->hOpenGL) {
|
||||
BOOL released = FreeLibrary(this->hOpenGL);
|
||||
if (released)
|
||||
printf("System OpenGL library released\n");
|
||||
else
|
||||
printf("System OpenGL library NOT released\n");
|
||||
this->hOpenGL = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
|
||||
{
|
||||
// printf("is_version_greater_or_equal_to, version: %s\n", version.c_str());
|
||||
std::vector<std::string> tokens;
|
||||
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
|
||||
if (tokens.empty())
|
||||
return false;
|
||||
|
||||
std::vector<std::string> numbers;
|
||||
boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on);
|
||||
|
||||
unsigned int gl_major = 0;
|
||||
unsigned int gl_minor = 0;
|
||||
if (numbers.size() > 0)
|
||||
gl_major = ::atoi(numbers[0].c_str());
|
||||
if (numbers.size() > 1)
|
||||
gl_minor = ::atoi(numbers[1].c_str());
|
||||
// printf("Major: %d, minor: %d\n", gl_major, gl_minor);
|
||||
if (gl_major < major)
|
||||
return false;
|
||||
else if (gl_major > major)
|
||||
return true;
|
||||
else
|
||||
return gl_minor >= minor;
|
||||
}
|
||||
|
||||
protected:
|
||||
static bool message_pump_exit;
|
||||
|
||||
void check(HWND hWnd)
|
||||
{
|
||||
hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0);
|
||||
if (hOpenGL == nullptr) {
|
||||
printf("Failed loading the system opengl32.dll\n");
|
||||
return;
|
||||
}
|
||||
|
||||
typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC);
|
||||
typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC);
|
||||
typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC);
|
||||
typedef GLubyte* (WINAPI *Func_glGetString )(GLenum);
|
||||
|
||||
Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext");
|
||||
Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent");
|
||||
Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext");
|
||||
Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString");
|
||||
|
||||
if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) {
|
||||
printf("Failed loading the system opengl32.dll: The library is invalid.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd =
|
||||
{
|
||||
sizeof(PIXELFORMATDESCRIPTOR),
|
||||
1,
|
||||
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
|
||||
PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
|
||||
32, // Color depth of the framebuffer.
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
24, // Number of bits for the depthbuffer
|
||||
8, // Number of bits for the stencilbuffer
|
||||
0, // Number of Aux buffers in the framebuffer.
|
||||
PFD_MAIN_PLANE,
|
||||
0,
|
||||
0, 0, 0
|
||||
};
|
||||
|
||||
HDC ourWindowHandleToDeviceContext = ::GetDC(hWnd);
|
||||
// Gdi32.dll
|
||||
int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd);
|
||||
// Gdi32.dll
|
||||
SetPixelFormat(ourWindowHandleToDeviceContext, letWindowsChooseThisPixelFormat, &pfd);
|
||||
// Opengl32.dll
|
||||
HGLRC glcontext = wglCreateContext(ourWindowHandleToDeviceContext);
|
||||
wglMakeCurrent(ourWindowHandleToDeviceContext, glcontext);
|
||||
// Opengl32.dll
|
||||
const char *data = (const char*)glGetString(GL_VERSION);
|
||||
if (data != nullptr)
|
||||
this->version = data;
|
||||
// printf("check -version: %s\n", version.c_str());
|
||||
data = (const char*)glGetString(0x8B8C); // GL_SHADING_LANGUAGE_VERSION
|
||||
if (data != nullptr)
|
||||
this->glsl_version = data;
|
||||
data = (const char*)glGetString(GL_VENDOR);
|
||||
if (data != nullptr)
|
||||
this->vendor = data;
|
||||
data = (const char*)glGetString(GL_RENDERER);
|
||||
if (data != nullptr)
|
||||
this->renderer = data;
|
||||
// Opengl32.dll
|
||||
wglDeleteContext(glcontext);
|
||||
::ReleaseDC(hWnd, ourWindowHandleToDeviceContext);
|
||||
this->success = true;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch(message)
|
||||
{
|
||||
case WM_CREATE:
|
||||
{
|
||||
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
OpenGLVersionCheck *ogl_data = reinterpret_cast<OpenGLVersionCheck*>(pCreate->lpCreateParams);
|
||||
ogl_data->check(hWnd);
|
||||
DestroyWindow(hWnd);
|
||||
return 0;
|
||||
}
|
||||
case WM_NCDESTROY:
|
||||
message_pump_exit = true;
|
||||
return 0;
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool OpenGLVersionCheck::message_pump_exit = false;
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
extern "C" {
|
||||
typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv);
|
||||
Slic3rMainFunc orcaslicer_main = nullptr;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
#ifdef SLIC3R_WRAPPER_NOCONSOLE
|
||||
int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, PWSTR /* lpCmdLine */, int /* nCmdShow */)
|
||||
{
|
||||
int argc;
|
||||
wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
#else
|
||||
int wmain(int argc, wchar_t **argv)
|
||||
{
|
||||
#endif
|
||||
// Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application.
|
||||
// Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and
|
||||
// the application will be killed even if "Ignore" button is pressed.
|
||||
_set_error_mode(_OUT_TO_MSGBOX);
|
||||
|
||||
std::vector<wchar_t*> argv_extended;
|
||||
argv_extended.emplace_back(argv[0]);
|
||||
|
||||
#ifdef SLIC3R_WRAPPER_GCODEVIEWER
|
||||
wchar_t gcodeviewer_param[] = L"--gcodeviewer";
|
||||
argv_extended.emplace_back(gcodeviewer_param);
|
||||
#endif /* SLIC3R_WRAPPER_GCODEVIEWER */
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
// Here one may push some additional parameters based on the wrapper type.
|
||||
bool force_mesa = false;
|
||||
#endif /* SLIC3R_GUI */
|
||||
for (int i = 1; i < argc; ++ i) {
|
||||
#ifdef SLIC3R_GUI
|
||||
if (wcscmp(argv[i], L"--sw-renderer") == 0)
|
||||
force_mesa = true;
|
||||
else if (wcscmp(argv[i], L"--no-sw-renderer") == 0)
|
||||
force_mesa = false;
|
||||
#endif /* SLIC3R_GUI */
|
||||
argv_extended.emplace_back(argv[i]);
|
||||
}
|
||||
argv_extended.emplace_back(nullptr);
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
OpenGLVersionCheck opengl_version_check;
|
||||
bool load_mesa =
|
||||
// Forced from the command line.
|
||||
force_mesa ||
|
||||
// Try to load the default OpenGL driver and test its context version.
|
||||
! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0);
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
wchar_t path_to_exe[MAX_PATH + 1] = { 0 };
|
||||
::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH);
|
||||
wchar_t drive[_MAX_DRIVE];
|
||||
wchar_t dir[_MAX_DIR];
|
||||
wchar_t fname[_MAX_FNAME];
|
||||
wchar_t ext[_MAX_EXT];
|
||||
_wsplitpath(path_to_exe, drive, dir, fname, ext);
|
||||
_wmakepath(path_to_exe, drive, dir, nullptr, nullptr);
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
|
||||
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
|
||||
if (load_mesa) {
|
||||
opengl_version_check.unload_opengl_dll();
|
||||
wchar_t path_to_mesa[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_mesa, path_to_exe);
|
||||
wcscat(path_to_mesa, L"mesa\\opengl32.dll");
|
||||
printf("Loading MESA OpenGL library: %S\n", path_to_mesa);
|
||||
HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0);
|
||||
if (hInstance_OpenGL == nullptr) {
|
||||
printf("MESA OpenGL library was not loaded\n");
|
||||
} else
|
||||
printf("MESA OpenGL library was loaded successfully\n");
|
||||
}
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
|
||||
wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_slic3r, path_to_exe);
|
||||
wcscat(path_to_slic3r, L"OrcaSlicer.dll");
|
||||
// printf("Loading Slic3r library: %S\n", path_to_slic3r);
|
||||
HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0);
|
||||
if (hInstance_Slic3r == nullptr) {
|
||||
printf("OrcaSlicer.dll was not loaded, error=%d\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// resolve function address here
|
||||
orcaslicer_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r,
|
||||
#ifdef _WIN64
|
||||
// there is just a single calling conversion, therefore no mangling of the function name.
|
||||
"orcaslicer_main"
|
||||
#else // stdcall calling convention declaration
|
||||
"_bambustu_main@8"
|
||||
#endif
|
||||
);
|
||||
if (orcaslicer_main == nullptr) {
|
||||
printf("could not locate the function orcaslicer_main in OrcaSlicer.dll\n");
|
||||
return -1;
|
||||
}
|
||||
// argc minus the trailing nullptr of the argv
|
||||
return orcaslicer_main((int)argv_extended.size() - 1, argv_extended.data());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
#include "BaseException.h"
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <mutex>
|
||||
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
static std::string g_log_folder;
|
||||
static std::atomic<int> g_crash_log_count = 0;
|
||||
static std::mutex g_dump_mutex;
|
||||
|
||||
CBaseException::CBaseException(HANDLE hProcess, WORD wPID, LPCTSTR lpSymbolPath, PEXCEPTION_POINTERS pEp):
|
||||
CStackWalker(hProcess, wPID, lpSymbolPath)
|
||||
{
|
||||
if (NULL != pEp)
|
||||
{
|
||||
m_pEp = new EXCEPTION_POINTERS;
|
||||
CopyMemory(m_pEp, pEp, sizeof(EXCEPTION_POINTERS));
|
||||
}
|
||||
output_file = new boost::nowide::ofstream();
|
||||
std::time_t t = std::time(0);
|
||||
std::tm* now_time = std::localtime(&t);
|
||||
std::stringstream buf;
|
||||
|
||||
if (!g_log_folder.empty()) {
|
||||
buf << std::put_time(now_time, "crash_%a_%b_%d_%H_%M_%S_") <<g_crash_log_count++ <<".log";
|
||||
auto log_folder = (boost::filesystem::path(g_log_folder) / "log").make_preferred();
|
||||
if (!boost::filesystem::exists(log_folder)) {
|
||||
boost::filesystem::create_directory(log_folder);
|
||||
}
|
||||
auto crash_log_path = boost::filesystem::path(log_folder / buf.str()).make_preferred();
|
||||
std::string log_filename = crash_log_path.string();
|
||||
output_file->open(log_filename, std::ios::out | std::ios::app);
|
||||
|
||||
// Output app build info in crash log so we could look for the correct PDB files
|
||||
OutputString(_T("%s\n\n"), _T(SLIC3R_APP_NAME " " SoftFever_VERSION " Build " GIT_COMMIT_HASH));
|
||||
}
|
||||
}
|
||||
|
||||
CBaseException::~CBaseException(void)
|
||||
{
|
||||
if (output_file) {
|
||||
output_file->close();
|
||||
delete output_file;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//BBS set crash log folder
|
||||
void CBaseException::set_log_folder(std::string log_folder)
|
||||
{
|
||||
g_log_folder = log_folder;
|
||||
}
|
||||
|
||||
void CBaseException::OutputString(LPCTSTR lpszFormat, ...)
|
||||
{
|
||||
TCHAR szBuf[2048] = _T("");
|
||||
va_list args;
|
||||
va_start(args, lpszFormat);
|
||||
_vsntprintf_s(szBuf, 2048, lpszFormat, args);
|
||||
va_end(args);
|
||||
|
||||
//WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), szBuf, _tcslen(szBuf), NULL, NULL);
|
||||
|
||||
//output it to the current directory of binary
|
||||
std::string output_str = textconv_helper::T2A_(szBuf);
|
||||
*output_file << output_str;
|
||||
output_file->flush();
|
||||
}
|
||||
|
||||
void CBaseException::ShowLoadModules()
|
||||
{
|
||||
LoadSymbol();
|
||||
LPMODULE_INFO pHead = GetLoadModules();
|
||||
LPMODULE_INFO pmi = pHead;
|
||||
|
||||
TCHAR szBuf[MAX_COMPUTERNAME_LENGTH] = _T("");
|
||||
DWORD dwSize = MAX_COMPUTERNAME_LENGTH;
|
||||
GetUserName(szBuf, &dwSize);
|
||||
OutputString(_T("Current User:%s\r\n"), szBuf);
|
||||
OutputString(_T("BaseAddress:\tSize:\tName\tPath\tSymbolPath\tVersion\r\n"));
|
||||
while (NULL != pmi)
|
||||
{
|
||||
OutputString(_T("%08x\t%d\t%s\t%s\t%s\t%s\r\n"), (unsigned long)(pmi->ModuleAddress), pmi->dwModSize, pmi->szModuleName, pmi->szModulePath, pmi->szSymbolPath, pmi->szVersion);
|
||||
pmi = pmi->pNext;
|
||||
}
|
||||
|
||||
FreeModuleInformations(pHead);
|
||||
}
|
||||
|
||||
void CBaseException::ShowCallstack(HANDLE hThread, const CONTEXT* context)
|
||||
{
|
||||
OutputString(_T("Show CallStack:\n"));
|
||||
LPSTACKINFO phead = StackWalker(hThread, context);
|
||||
|
||||
// Show RVA of each call stack, so we can locate the symbol using pdb file
|
||||
// To show the symbol, load the <szFaultingModule> in WinDBG with pdb file, then type the following commands:
|
||||
// > lm which gives you the start address of each module, as well as module names
|
||||
// > !dh <module name> list all module headers. Find the <virtual address> of the section given by
|
||||
// the <section> output in the crash log
|
||||
// > ln <module start address> + <section virtual address> + <offset> reveals the debug symbol
|
||||
OutputString(_T("\nLogical Address:\n"));
|
||||
TCHAR szFaultingModule[MAX_PATH];
|
||||
DWORD section, offset;
|
||||
for (LPSTACKINFO ps = phead; ps != nullptr; ps = ps->pNext) {
|
||||
if (GetLogicalAddress((PVOID) ps->szFncAddr, szFaultingModule, sizeof(szFaultingModule), section, offset)) {
|
||||
OutputString(_T("0x%X 0x%X:0x%X %s\n"), ps->szFncAddr, section, offset, szFaultingModule);
|
||||
} else {
|
||||
OutputString(_T("0x%X Unknown\n"), ps->szFncAddr);
|
||||
}
|
||||
}
|
||||
|
||||
FreeStackInformations(phead);
|
||||
}
|
||||
|
||||
void CBaseException::ShowExceptionResoult(DWORD dwExceptionCode)
|
||||
{
|
||||
OutputString(_T("Exception Code :%08x "), dwExceptionCode);
|
||||
// BBS: to be checked
|
||||
#if 1
|
||||
switch (dwExceptionCode)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
{
|
||||
//OutputString(_T("ACCESS_VIOLATION(%s)\r\n"), _T("��д�Ƿ��ڴ�"));
|
||||
OutputString(_T("ACCESS_VIOLATION\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
{
|
||||
//OutputString(_T("DATATYPE_MISALIGNMENT(%s)\r\n"), _T("�߳���ͼ�ڲ�֧�ֶ����Ӳ���϶�дδ���������"));
|
||||
OutputString(_T("DATATYPE_MISALIGNMENT\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
{
|
||||
//OutputString(_T("BREAKPOINT(%s)\r\n"), _T("����һ���ϵ�"));
|
||||
OutputString(_T("BREAKPOINT\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
{
|
||||
//OutputString(_T("SINGLE_STEP(%s)\r\n"), _T("����")); //һ���Ƿ����ڵ����¼���
|
||||
OutputString(_T("SINGLE_STEP\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
{
|
||||
//OutputString(_T("ARRAY_BOUNDS_EXCEEDED(%s)\r\n"), _T("�������Խ��"));
|
||||
OutputString(_T("ARRAY_BOUNDS_EXCEEDED\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
{
|
||||
//OutputString(_T("FLT_DENORMAL_OPERAND(%s)\r\n"), _T("���������һ�������������棬�����ĸ���������ʾ")); //������������
|
||||
OutputString(_T("FLT_DENORMAL_OPERAND\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
{
|
||||
//OutputString(_T("FLT_DIVIDE_BY_ZERO(%s)\r\n"), _T("��������0����"));
|
||||
OutputString(_T("FLT_DIVIDE_BY_ZERO\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
{
|
||||
//OutputString(_T("FLT_INEXACT_RESULT(%s)\r\n"), _T("�����������Ľ������ʾ")); //����ʾһ��������̫С��������������ʾ�ķ�Χ, ����֮������Ľ���쳣
|
||||
OutputString(_T("FLT_INEXACT_RESULT\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
{
|
||||
//OutputString(_T("FLT_INVALID_OPERATION(%s)\r\n"), _T("�����������쳣"));
|
||||
OutputString(_T("FLT_INVALID_OPERATION\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
{
|
||||
//OutputString(_T("FLT_OVERFLOW(%s)\r\n"), _T("���������ָ����������Ӧ���͵����ֵ"));
|
||||
OutputString(_T("FLT_OVERFLOW\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
{
|
||||
//OutputString(_T("STACK_CHECK(%s)\r\n"), _T("ջԽ�����ջ�������"));
|
||||
OutputString(_T("STACK_CHECK\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
{
|
||||
//OutputString(_T("INT_DIVIDE_BY_ZERO(%s)\r\n"), _T("������0�쳣"));
|
||||
OutputString(_T("INT_DIVIDE_BY_ZERO\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_INVALID_HANDLE:
|
||||
{
|
||||
//OutputString(_T("INVALID_HANDLE(%s)\r\n"), _T("�����Ч"));
|
||||
OutputString(_T("INVALID_HANDLE\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
{
|
||||
//OutputString(_T("PRIV_INSTRUCTION(%s)\r\n"), _T("�߳���ͼִ�е�ǰ����ģʽ��֧�ֵ�ָ��"));
|
||||
OutputString(_T("PRIV_INSTRUCTION\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
{
|
||||
//OutputString(_T("IN_PAGE_ERROR(%s)\r\n"), _T("�߳���ͼ����δ���ص������ڴ�ҳ���߲��ܼ��ص������ڴ�ҳ"));
|
||||
OutputString(_T("IN_PAGE_ERROR\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
{
|
||||
//OutputString(_T("ILLEGAL_INSTRUCTION(%s)\r\n"), _T("�߳���ͼִ����Чָ��"));
|
||||
OutputString(_T("ILLEGAL_INSTRUCTION\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
{
|
||||
//OutputString(_T("NONCONTINUABLE_EXCEPTION(%s)\r\n"), _T("�߳���ͼ��һ�����ɼ���ִ�е��쳣���������ִ��"));
|
||||
OutputString(_T("NONCONTINUABLE_EXCEPTION\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
{
|
||||
//OutputString(_T("STACK_OVERFLOW(%s)\r\n"), _T("ջ���"));
|
||||
OutputString(_T("STACK_OVERFLOW\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
{
|
||||
//OutputString(_T("INVALID_DISPOSITION(%s)\r\n"), _T("�쳣���������쳣������������һ����Ч����")); //ʹ�ø����Ա�д�ij�����Զ������������쳣
|
||||
OutputString(_T("INVALID_DISPOSITION\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
{
|
||||
//OutputString(_T("FLT_UNDERFLOW(%s)\r\n"), _T("������������ָ��С����Ӧ���͵���Сֵ"));
|
||||
OutputString(_T("FLT_UNDERFLOW\r\n"));
|
||||
}
|
||||
return ;
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
{
|
||||
//OutputString(_T("INT_OVERFLOW(%s)\r\n"), _T("��������Խ��"));
|
||||
OutputString(_T("INT_OVERFLOW\r\n"));
|
||||
}
|
||||
return ;
|
||||
}
|
||||
|
||||
TCHAR szBuffer[512] = { 0 };
|
||||
|
||||
FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
|
||||
GetModuleHandle( _T("NTDLL.DLL") ),
|
||||
dwExceptionCode, 0, szBuffer, sizeof( szBuffer ), 0 );
|
||||
|
||||
OutputString(_T("%s"), szBuffer);
|
||||
OutputString(_T("\r\n"));
|
||||
#endif
|
||||
}
|
||||
|
||||
LONG WINAPI CBaseException::UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo )
|
||||
{
|
||||
if (pExceptionInfo->ExceptionRecord->ExceptionCode < 0x80000000
|
||||
//BBS: Load project on computers with SDC may trigger this exception (in ShowModal()),
|
||||
// It's not fatal and should be ignored, or there will be lots of meaningless crash logs
|
||||
|| pExceptionInfo->ExceptionRecord->ExceptionCode==0xe0434352)
|
||||
//BBS: ignore the exception when copy preset
|
||||
//|| pExceptionInfo->ExceptionRecord->ExceptionCode==0xe06d7363)
|
||||
{
|
||||
//BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": got an ExceptionCode %1%, skip it!") % pExceptionInfo->ExceptionRecord->ExceptionCode;
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
g_dump_mutex.lock();
|
||||
CBaseException base(GetCurrentProcess(), GetCurrentProcessId(), NULL, pExceptionInfo);
|
||||
base.ShowExceptionInformation();
|
||||
g_dump_mutex.unlock();
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
LONG WINAPI CBaseException::UnhandledExceptionFilter2(PEXCEPTION_POINTERS pExceptionInfo )
|
||||
{
|
||||
CBaseException base(GetCurrentProcess(), GetCurrentProcessId(), NULL, pExceptionInfo);
|
||||
base.ShowExceptionInformation();
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
BOOL CBaseException::GetLogicalAddress(
|
||||
PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset )
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
|
||||
if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) )
|
||||
return FALSE;
|
||||
|
||||
DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase;
|
||||
|
||||
if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )
|
||||
return FALSE;
|
||||
|
||||
if (!hMod)
|
||||
return FALSE;
|
||||
|
||||
PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
|
||||
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);
|
||||
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
|
||||
|
||||
DWORD_PTR rva = (DWORD_PTR)addr - hMod;
|
||||
|
||||
//���㵱ǰ��ַ�ڵڼ�����
|
||||
for (unsigned i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++ )
|
||||
{
|
||||
DWORD sectionStart = pSection->VirtualAddress;
|
||||
DWORD sectionEnd = sectionStart + std::max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);
|
||||
|
||||
if ( (rva >= sectionStart) && (rva <= sectionEnd) )
|
||||
{
|
||||
section = i+1;
|
||||
offset = rva - sectionStart;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE; // Should never get here!
|
||||
}
|
||||
|
||||
void CBaseException::ShowRegistorInformation(PCONTEXT pCtx)
|
||||
{
|
||||
#if defined(_M_IX86) // Intel Only!
|
||||
OutputString( _T("\nRegisters:\r\n") );
|
||||
|
||||
OutputString(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n"),
|
||||
pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi );
|
||||
|
||||
OutputString( _T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip );
|
||||
OutputString( _T("SS:ESP:%04X:%08X EBP:%08X\r\n"),pCtx->SegSs, pCtx->Esp, pCtx->Ebp );
|
||||
OutputString( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );
|
||||
OutputString( _T("Flags:%08X\r\n"), pCtx->EFlags );
|
||||
#elif defined(_M_X64)
|
||||
OutputString(_T("\nRegisters:\r\n"));
|
||||
|
||||
OutputString(_T("RAX:%016llX\r\nRBX:%016llX\r\nRCX:%016llX\r\nRDX:%016llX\r\nRSI:%016llX\r\nRDI:%016llX\r\n"),
|
||||
pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, pCtx->Rsi, pCtx->Rdi );
|
||||
|
||||
OutputString(_T("R8:%016llX\r\nR9:%016llX\r\nR10:%016llX\r\nR11:%016llX\r\nR12:%016llX\r\nR13:%016llX\r\nR14:%016llX\r\nR15:%016llX\r\n"),
|
||||
pCtx->R8, pCtx->R9, pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13, pCtx->R14, pCtx->R15 );
|
||||
|
||||
OutputString(_T("CS:RIP:%04X:%016llX\r\n"), pCtx->SegCs, pCtx->Rip);
|
||||
OutputString(_T("SS:RSP:%04X:%016llX RBP:%016llX\r\n"), pCtx->SegSs, pCtx->Rsp, pCtx->Rbp);
|
||||
OutputString(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs);
|
||||
OutputString(_T("Flags:%08X\r\n"), pCtx->EFlags);
|
||||
#endif
|
||||
|
||||
OutputString( _T("\r\n") );
|
||||
}
|
||||
|
||||
void CBaseException::STF(unsigned int ui, PEXCEPTION_POINTERS pEp)
|
||||
{
|
||||
CBaseException base(GetCurrentProcess(), GetCurrentProcessId(), NULL, pEp);
|
||||
throw base;
|
||||
}
|
||||
|
||||
void CBaseException::ShowExceptionInformation()
|
||||
{
|
||||
OutputString(_T("Exceptions:\r\n"));
|
||||
ShowExceptionResoult(m_pEp->ExceptionRecord->ExceptionCode);
|
||||
|
||||
OutputString(_T("Exception Flag :0x%x "), m_pEp->ExceptionRecord->ExceptionFlags);
|
||||
OutputString(_T("NumberParameters :%ld \n"), m_pEp->ExceptionRecord->NumberParameters);
|
||||
for (int i = 0; i < m_pEp->ExceptionRecord->NumberParameters; i++)
|
||||
{
|
||||
OutputString(_T("Param %d :0x%x \n"), i, m_pEp->ExceptionRecord->ExceptionInformation[i]);
|
||||
}
|
||||
OutputString(_T("Context :%p \n"), m_pEp->ContextRecord);
|
||||
OutputString(_T("ContextFlag : 0x%x, EFlags: 0x%x \n"), m_pEp->ContextRecord->ContextFlags, m_pEp->ContextRecord->EFlags);
|
||||
|
||||
TCHAR szFaultingModule[MAX_PATH];
|
||||
DWORD section, offset;
|
||||
GetLogicalAddress(m_pEp->ExceptionRecord->ExceptionAddress, szFaultingModule, sizeof(szFaultingModule), section, offset );
|
||||
OutputString( _T("Fault address: 0x%X 0x%X:0x%X %s\r\n"), m_pEp->ExceptionRecord->ExceptionAddress, section, offset, szFaultingModule );
|
||||
|
||||
ShowRegistorInformation(m_pEp->ContextRecord);
|
||||
|
||||
ShowCallstack(GetCurrentThread(), m_pEp->ContextRecord);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "stackwalker.h"
|
||||
#include <eh.h>
|
||||
|
||||
class CBaseException : public CStackWalker
|
||||
{
|
||||
public:
|
||||
CBaseException(HANDLE hProcess = GetCurrentProcess(), WORD wPID = GetCurrentProcessId(), LPCTSTR lpSymbolPath = NULL, PEXCEPTION_POINTERS pEp = NULL);
|
||||
~CBaseException(void);
|
||||
virtual void OutputString(LPCTSTR lpszFormat, ...);
|
||||
virtual void ShowLoadModules();
|
||||
virtual void ShowCallstack(HANDLE hThread = GetCurrentThread(), const CONTEXT* context = NULL);
|
||||
virtual void ShowExceptionResoult(DWORD dwExceptionCode);
|
||||
virtual BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset );
|
||||
virtual void ShowRegistorInformation(PCONTEXT pCtx);
|
||||
virtual void ShowExceptionInformation();
|
||||
static LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);
|
||||
static LONG WINAPI UnhandledExceptionFilter2(PEXCEPTION_POINTERS pExceptionInfo);
|
||||
static void STF(unsigned int ui, PEXCEPTION_POINTERS pEp);
|
||||
//BBS set crash log folder
|
||||
static void set_log_folder(std::string log_folder);
|
||||
protected:
|
||||
PEXCEPTION_POINTERS m_pEp;
|
||||
boost::nowide::ofstream *output_file;
|
||||
};
|
||||
|
||||
#define SET_DEFULTER_HANDLER() SetUnhandledExceptionFilter(CBaseException::UnhandledExceptionFilter)
|
||||
|
||||
#define SET_DEFAUL_EXCEPTION() _set_se_translator(CBaseException::STF)
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
option(SLIC3R_ENC_CHECK "Verify encoding of source files" 1)
|
||||
|
||||
if (IS_CROSS_COMPILE)
|
||||
# Force disable due to cross compilation. This fact is already printed on cli for users
|
||||
set(SLIC3R_ENC_CHECK OFF CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
|
||||
if (SLIC3R_ENC_CHECK)
|
||||
add_executable(encoding-check encoding-check.cpp)
|
||||
|
||||
# A global no-op target which depends on all encodings checks,
|
||||
# and on which in turn all checked targets depend.
|
||||
# This is done to make encoding checks the first thing to be
|
||||
# performed before actually compiling any sources of the checked targets
|
||||
# to make the check fail as early as possible.
|
||||
add_custom_target(global-encoding-check
|
||||
ALL
|
||||
DEPENDS encoding-check
|
||||
)
|
||||
endif()
|
||||
|
||||
# Function that adds source file encoding check to a target
|
||||
# using the above encoding-check binary
|
||||
|
||||
function(encoding_check TARGET)
|
||||
if (SLIC3R_ENC_CHECK)
|
||||
# Obtain target source files
|
||||
get_target_property(T_SOURCES ${TARGET} SOURCES)
|
||||
|
||||
# Define top-level encoding check target for this ${TARGET}
|
||||
add_custom_target(encoding-check-${TARGET}
|
||||
DEPENDS encoding-check ${T_SOURCES}
|
||||
COMMENT "Checking source files encodings for target ${TARGET}"
|
||||
)
|
||||
|
||||
# Add checking of each source file as a subcommand of encoding-check-${TARGET}
|
||||
foreach(file ${T_SOURCES})
|
||||
add_custom_command(TARGET encoding-check-${TARGET} PRE_BUILD
|
||||
COMMAND $<TARGET_FILE:encoding-check> ${TARGET} ${file}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# This adds dependency on encoding-check-${TARGET} to ${TARET}
|
||||
# via the global-encoding-check
|
||||
add_dependencies(global-encoding-check encoding-check-${TARGET})
|
||||
add_dependencies(${TARGET} global-encoding-check)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,174 @@
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
void generate_custom_presets(PresetBundle* preset_bundle, AppConfig& app_config)
|
||||
{
|
||||
struct cus_preset
|
||||
{
|
||||
std::string name;
|
||||
std::string parent_name;
|
||||
};
|
||||
// create user presets
|
||||
auto createCustomPrinters = [&](Preset::Type type) {
|
||||
std::vector<cus_preset> custom_preset;
|
||||
PresetCollection* collection = nullptr;
|
||||
if (type == Preset::TYPE_PRINT)
|
||||
collection = &preset_bundle->prints;
|
||||
else if (type == Preset::TYPE_FILAMENT)
|
||||
collection = &preset_bundle->filaments;
|
||||
else if (type == Preset::TYPE_PRINTER)
|
||||
collection = &preset_bundle->printers;
|
||||
else
|
||||
return;
|
||||
custom_preset.reserve(collection->size());
|
||||
for (auto& parent : collection->get_presets()) {
|
||||
if (!parent.is_system)
|
||||
continue;
|
||||
auto new_name = parent.name + "_orca_test";
|
||||
if (parent.vendor)
|
||||
new_name = parent.vendor->name + "_" + new_name;
|
||||
custom_preset.push_back({new_name, parent.name});
|
||||
}
|
||||
for (auto p : custom_preset) {
|
||||
// Creating a new preset.
|
||||
auto parent = collection->find_preset(p.parent_name);
|
||||
auto vendor = collection->get_preset_with_vendor_profile(*parent);
|
||||
if (type == Preset::TYPE_FILAMENT) {
|
||||
parent->config.set_key_value("filament_start_gcode",
|
||||
new ConfigOptionStrings({"this_is_orca_test_filament_start_gcode_mock"}));
|
||||
parent->config.set_key_value("filament_notes", new ConfigOptionString(vendor.vendor->name));
|
||||
} else if (type == Preset::TYPE_PRINT) {
|
||||
parent->config.set_key_value("filename_format", new ConfigOptionString("this_is_orca_test_filename_format_mock"));
|
||||
parent->config.set_key_value("notes", new ConfigOptionString(vendor.vendor->name));
|
||||
} else if (type == Preset::TYPE_PRINTER) {
|
||||
parent->config.set_key_value("machine_start_gcode", new ConfigOptionString("this_is_orca_test_machine_start_gcode_mock"));
|
||||
parent->config.set_key_value("printer_notes", new ConfigOptionString(vendor.vendor->name));
|
||||
}
|
||||
|
||||
collection->save_current_preset(p.name, false, false, parent);
|
||||
|
||||
}
|
||||
};
|
||||
createCustomPrinters(Preset::TYPE_PRINTER);
|
||||
createCustomPrinters(Preset::TYPE_FILAMENT);
|
||||
createCustomPrinters(Preset::TYPE_PRINT);
|
||||
|
||||
std::string user_sub_folder = DEFAULT_USER_FOLDER_NAME;
|
||||
const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + user_sub_folder;
|
||||
|
||||
fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
if (!fs::exists(user_folder))
|
||||
fs::create_directory(user_folder);
|
||||
|
||||
fs::path folder(dir_user_presets);
|
||||
if (!fs::exists(folder))
|
||||
fs::create_directory(folder);
|
||||
std::vector<std::string> need_to_delete_list; // store setting ids of preset
|
||||
|
||||
preset_bundle->prints.save_user_presets(dir_user_presets, PRESET_PRINT_NAME, need_to_delete_list);
|
||||
preset_bundle->filaments.save_user_presets(dir_user_presets, PRESET_FILAMENT_NAME, need_to_delete_list);
|
||||
preset_bundle->printers.save_user_presets(dir_user_presets, PRESET_PRINTER_NAME, need_to_delete_list);
|
||||
|
||||
std::cout << "Custom presets generated successfully" << std::endl;
|
||||
}
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
po::options_description desc("Orca Profile Validator\nUsage");
|
||||
// clang-format off
|
||||
desc.add_options()("help,h", "help")
|
||||
#ifdef __APPLE__
|
||||
("path,p", po::value<std::string>()->default_value("../../../../../../../resources/profiles"), "profile folder")
|
||||
#else
|
||||
("path,p", po::value<std::string>()->default_value("../../../resources/profiles"), "profile folder")
|
||||
#endif
|
||||
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified")
|
||||
("generate_presets,g", po::value<bool>()->default_value(false), "Generate user presets for mock test")
|
||||
("log_level,l", po::value<int>()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs.");
|
||||
// clang-format on
|
||||
|
||||
po::variables_map vm;
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cout << desc << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
po::notify(vm);
|
||||
} catch (const po::error& e) {
|
||||
std::cerr << "Error: " << e.what() << "\n";
|
||||
std::cerr << desc << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string path = vm["path"].as<std::string>();
|
||||
std::string vendor = vm["vendor"].as<std::string>();
|
||||
int log_level = vm["log_level"].as<int>();
|
||||
bool generate_user_preset = vm["generate_presets"].as<bool>();
|
||||
|
||||
// check if path is valid, and return error if not
|
||||
if (!fs::exists(path) || !fs::is_directory(path)) {
|
||||
std::cerr << "Error: " << path << " is not a valid directory\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// std::cout<<"path: "<<path<<std::endl;
|
||||
// std::cout<<"vendor: "<<vendor<<std::endl;
|
||||
// std::cout<<"log_level: "<<log_level<<std::endl;
|
||||
|
||||
set_data_dir(path);
|
||||
|
||||
auto user_dir = fs::path(Slic3r::data_dir()) / PRESET_USER_DIR;
|
||||
user_dir.make_preferred();
|
||||
if (!fs::exists(user_dir))
|
||||
fs::create_directory(user_dir);
|
||||
|
||||
set_logging_level(log_level);
|
||||
auto preset_bundle = new PresetBundle();
|
||||
// preset_bundle->setup_directories();
|
||||
preset_bundle->set_is_validation_mode(true);
|
||||
preset_bundle->set_vendor_to_validate(vendor);
|
||||
|
||||
preset_bundle->set_default_suppressed(true);
|
||||
AppConfig app_config;
|
||||
app_config.set("preset_folder", "default");
|
||||
|
||||
if(generate_user_preset)
|
||||
preset_bundle->remove_user_presets_directory("default");
|
||||
|
||||
try {
|
||||
auto preset_substitutions = preset_bundle->load_presets(app_config, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << ex.what();
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
// Report loaded presets
|
||||
std::cout << "Total loaded vendors: " << preset_bundle->vendors.size() << std::endl;
|
||||
|
||||
if (generate_user_preset) {
|
||||
generate_custom_presets(preset_bundle, app_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (preset_bundle->has_errors()) {
|
||||
std::cout << "Validation failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Validation completed successfully" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
#include "StackWalker.h"
|
||||
#include <strsafe.h>
|
||||
//#include <atlconv.h>
|
||||
#include <dbghelp.h>
|
||||
#pragma comment(lib, "version.lib")
|
||||
#pragma comment( lib, "dbghelp.lib" )
|
||||
|
||||
CStackWalker::CStackWalker(HANDLE hProcess, WORD wPID, LPCTSTR lpSymbolPath):
|
||||
m_hProcess(hProcess),
|
||||
m_wPID(wPID),
|
||||
m_bSymbolLoaded(FALSE),
|
||||
m_lpszSymbolPath(NULL)
|
||||
{
|
||||
if (NULL != lpSymbolPath)
|
||||
{
|
||||
size_t dwLength = 0;
|
||||
StringCchLength(lpSymbolPath, MAX_SYMBOL_PATH, &dwLength);
|
||||
m_lpszSymbolPath = new TCHAR[dwLength + 1];
|
||||
ZeroMemory(m_lpszSymbolPath, sizeof(TCHAR) * (dwLength + 1));
|
||||
StringCchCopy(m_lpszSymbolPath, dwLength, lpSymbolPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CStackWalker::~CStackWalker(void)
|
||||
{
|
||||
if (NULL != m_lpszSymbolPath)
|
||||
{
|
||||
delete[] m_lpszSymbolPath;
|
||||
}
|
||||
|
||||
if (m_bSymbolLoaded)
|
||||
{
|
||||
SymCleanup(m_hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CStackWalker::LoadSymbol()
|
||||
{
|
||||
//USES_CONVERSION;
|
||||
//只加载一次
|
||||
if(m_bSymbolLoaded)
|
||||
{
|
||||
return m_bSymbolLoaded;
|
||||
}
|
||||
|
||||
if (NULL != m_lpszSymbolPath)
|
||||
{
|
||||
|
||||
m_bSymbolLoaded = SymInitialize(m_hProcess, textconv_helper::T2A_(m_lpszSymbolPath), FALSE);
|
||||
return m_bSymbolLoaded;
|
||||
}
|
||||
|
||||
//添加当前程序路径
|
||||
TCHAR szSymbolPath[MAX_SYMBOL_PATH] = _T("");
|
||||
StringCchCopy(szSymbolPath, MAX_SYMBOL_PATH, _T(".;"));
|
||||
|
||||
//添加程序所在目录
|
||||
TCHAR szTemp[MAX_PATH] = _T("");
|
||||
if (GetCurrentDirectory(MAX_PATH, szTemp) > 0)
|
||||
{
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T(";"));
|
||||
}
|
||||
|
||||
//添加程序主模块所在路径
|
||||
ZeroMemory(szTemp, MAX_PATH * sizeof(TCHAR));
|
||||
if (GetModuleFileName(NULL, szTemp, MAX_PATH) > 0)
|
||||
{
|
||||
size_t sLength = 0;
|
||||
StringCchLength(szTemp, MAX_PATH, &sLength);
|
||||
for (int i = sLength; i >= 0; i--)
|
||||
{
|
||||
if (szTemp[i] == _T('\\') || szTemp[i] == _T('/') || szTemp[i] == _T(':'))
|
||||
{
|
||||
szTemp[i] = _T('\0');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T(";"));
|
||||
|
||||
ZeroMemory(szTemp, MAX_PATH * sizeof(TCHAR));
|
||||
if (GetEnvironmentVariable(_T("_NT_SYMBOL_PATH"), szTemp, MAX_PATH) > 0)
|
||||
{
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T(";"));
|
||||
}
|
||||
|
||||
ZeroMemory(szTemp, MAX_PATH * sizeof(TCHAR));
|
||||
if (GetEnvironmentVariable(_T("_NT_ALTERNATE_SYMBOL_PATH"), szTemp, MAX_PATH) > 0)
|
||||
{
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T(";"));
|
||||
}
|
||||
|
||||
ZeroMemory(szTemp, MAX_PATH * sizeof(TCHAR));
|
||||
if (GetEnvironmentVariable(_T("SYSTEMROOT"), szTemp, MAX_PATH) > 0)
|
||||
{
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T(";"));
|
||||
// also add the "system32"-directory:
|
||||
StringCchCat(szTemp, MAX_PATH, _T("\\system32"));
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T(";"));
|
||||
}
|
||||
|
||||
ZeroMemory(szTemp, MAX_PATH * sizeof(TCHAR));
|
||||
if (GetEnvironmentVariable(_T("SYSTEMDRIVE"), szTemp, MAX_PATH) > 0)
|
||||
{
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T("SRV*"));
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, szTemp);
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T("\\websymbols"));
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T("*http://msdl.microsoft.com/download/symbols;"));
|
||||
}
|
||||
else
|
||||
{
|
||||
StringCchCat(szSymbolPath, MAX_SYMBOL_PATH, _T("SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;"));
|
||||
}
|
||||
|
||||
size_t sLength = 0;
|
||||
StringCchLength(szSymbolPath, MAX_SYMBOL_PATH, &sLength);
|
||||
if (sLength > 0)
|
||||
{
|
||||
m_lpszSymbolPath = new TCHAR[sLength + 1];
|
||||
ZeroMemory(m_lpszSymbolPath, sizeof(TCHAR) * (sLength + 1));
|
||||
StringCchCopy(m_lpszSymbolPath, sLength, szSymbolPath);
|
||||
}
|
||||
|
||||
if (NULL != m_lpszSymbolPath)
|
||||
{
|
||||
m_bSymbolLoaded = SymInitialize(m_hProcess, textconv_helper::T2A_(m_lpszSymbolPath), TRUE); //这里设置为TRUE,让它在初始化符号表的同时加载符号表
|
||||
}
|
||||
|
||||
DWORD symOptions = SymGetOptions();
|
||||
symOptions |= SYMOPT_LOAD_LINES;
|
||||
symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
|
||||
symOptions |= SYMOPT_DEBUG;
|
||||
SymSetOptions(symOptions);
|
||||
|
||||
return m_bSymbolLoaded;
|
||||
}
|
||||
|
||||
LPMODULE_INFO CStackWalker::GetLoadModules()
|
||||
{
|
||||
LPMODULE_INFO pHead = GetModulesTH32();
|
||||
if (NULL == pHead)
|
||||
{
|
||||
pHead = GetModulesPSAPI();
|
||||
}
|
||||
|
||||
return pHead;
|
||||
}
|
||||
|
||||
void CStackWalker::FreeModuleInformations(LPMODULE_INFO pmi)
|
||||
{
|
||||
LPMODULE_INFO head = pmi;
|
||||
while (NULL != head)
|
||||
{
|
||||
pmi = pmi->pNext;
|
||||
delete head;
|
||||
head = pmi;
|
||||
}
|
||||
}
|
||||
|
||||
LPMODULE_INFO CStackWalker::GetModulesTH32()
|
||||
{
|
||||
//这里为了防止加载Toolhelp.dll 影响最终结果,所以采用动态加载的方式
|
||||
LPMODULE_INFO pHead = NULL;
|
||||
LPMODULE_INFO pTail = pHead;
|
||||
|
||||
typedef HANDLE (WINAPI *pfnCreateToolhelp32Snapshot)(DWORD dwFlags, DWORD th32ProcessID);
|
||||
typedef BOOL (WINAPI *pfnModule32First)(HANDLE hSnapshot, LPMODULEENTRY32 lpme );
|
||||
typedef BOOL (WINAPI *pfnModule32Next)(HANDLE hSnapshot, LPMODULEENTRY32 lpme );
|
||||
|
||||
const TCHAR* dllname[] = {_T("kernel32.dll"), _T("tlhelp32.dll")};
|
||||
HINSTANCE hToolhelp = NULL;
|
||||
|
||||
pfnCreateToolhelp32Snapshot CreateToolhelp32Snapshot = NULL;
|
||||
pfnModule32First Module32First = NULL;
|
||||
pfnModule32Next Module32Next = NULL;
|
||||
|
||||
HANDLE hSnap;
|
||||
MODULEENTRY32 me;
|
||||
me.dwSize = sizeof(me);
|
||||
BOOL keepGoing;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < (sizeof(dllname) / sizeof(dllname[0])); i++)
|
||||
{
|
||||
hToolhelp = LoadLibrary(dllname[i]);
|
||||
if (hToolhelp == NULL)
|
||||
continue;
|
||||
CreateToolhelp32Snapshot = (pfnCreateToolhelp32Snapshot)GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot");
|
||||
#ifdef UNICODE
|
||||
Module32First = (pfnModule32First)GetProcAddress(hToolhelp, "Module32FirstW");
|
||||
Module32Next = (pfnModule32Next)GetProcAddress(hToolhelp, "Module32NextW");
|
||||
#else
|
||||
Module32First = (pfnModule32First)GetProcAddress(hToolhelp, "Module32FirstA");
|
||||
Module32Next = (pfnModule32Next)GetProcAddress(hToolhelp, "Module32NextA");
|
||||
#endif
|
||||
|
||||
if ((CreateToolhelp32Snapshot != NULL) && (Module32First != NULL) && (Module32Next != NULL))
|
||||
break;
|
||||
|
||||
FreeLibrary(hToolhelp);
|
||||
hToolhelp = NULL;
|
||||
}
|
||||
|
||||
if (hToolhelp == NULL)
|
||||
return pHead;
|
||||
|
||||
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_wPID);
|
||||
|
||||
if (hSnap == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
FreeLibrary(hToolhelp);
|
||||
return pHead;
|
||||
}
|
||||
|
||||
keepGoing = Module32First(hSnap, &me);
|
||||
|
||||
while (keepGoing)
|
||||
{
|
||||
LPMODULE_INFO pmi = new MODULE_INFO;
|
||||
ZeroMemory(pmi, sizeof(MODULE_INFO));
|
||||
|
||||
pmi->dwModSize = me.modBaseSize;
|
||||
pmi->ModuleAddress = (DWORD64)me.modBaseAddr;
|
||||
StringCchCopy(pmi->szModuleName, MAX_MODULE_NAME32, me.szModule);
|
||||
StringCchCopy(pmi->szModulePath, MAX_PATH, me.szExePath);
|
||||
GetModuleInformation(pmi);
|
||||
if (pHead == NULL)
|
||||
{
|
||||
pHead = pmi;
|
||||
pTail = pHead;
|
||||
}else
|
||||
{
|
||||
pTail->pNext = pmi;
|
||||
pTail = pmi;
|
||||
}
|
||||
|
||||
keepGoing = Module32Next(hSnap, &me);
|
||||
}
|
||||
|
||||
CloseHandle(hSnap);
|
||||
FreeLibrary(hToolhelp);
|
||||
return pHead;
|
||||
}
|
||||
|
||||
LPMODULE_INFO CStackWalker::GetModulesPSAPI()
|
||||
{
|
||||
LPMODULE_INFO pHead = NULL;
|
||||
LPMODULE_INFO pTail = pHead;
|
||||
typedef BOOL(WINAPI *pfnEnumProcessModules)(HANDLE hProcess, HMODULE * lphModule, DWORD cb,LPDWORD lpcbNeeded);
|
||||
typedef DWORD(WINAPI *pfnGetModuleFileNameEx)(HANDLE hProcess, HMODULE hModule, LPTSTR lpFilename, DWORD nSize);
|
||||
typedef DWORD(WINAPI *pfnGetModuleBaseName)(HANDLE hProcess, HMODULE hModule, LPTSTR lpFilename, DWORD nSize);
|
||||
typedef BOOL(WINAPI *pfnGetModuleInformation)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize);
|
||||
|
||||
HINSTANCE hPsapi;
|
||||
pfnEnumProcessModules EnumProcessModules = NULL;
|
||||
pfnGetModuleFileNameEx GetModuleFileNameEx = NULL;
|
||||
pfnGetModuleBaseName GetModuleBaseName = NULL;
|
||||
pfnGetModuleInformation GetModuleInformation = NULL;
|
||||
|
||||
DWORD i;
|
||||
//ModuleEntry e;
|
||||
DWORD cbNeeded;
|
||||
MODULEINFO mi;
|
||||
HMODULE* hMods = NULL;
|
||||
TCHAR szModuleName[MAX_MODULE_NAME32 + 1] = _T("");
|
||||
TCHAR szModulePath[MAX_PATH] = _T("");
|
||||
|
||||
hPsapi = LoadLibrary(_T("psapi.dll"));
|
||||
if (hPsapi == NULL)
|
||||
{
|
||||
return pHead;
|
||||
}
|
||||
|
||||
EnumProcessModules = (pfnEnumProcessModules)GetProcAddress(hPsapi, "EnumProcessModules");
|
||||
#ifdef UNICODE
|
||||
GetModuleFileNameEx = (pfnGetModuleFileNameEx)GetProcAddress(hPsapi, "GetModuleFileNameExW");
|
||||
GetModuleBaseName = (pfnGetModuleBaseName)GetProcAddress(hPsapi, "GetModuleBaseNameW");
|
||||
#else
|
||||
GetModuleFileNameEx = (pfnGetModuleFileNameEx)GetProcAddress(hPsapi, "GetModuleFileNameExA");
|
||||
GetModuleBaseName = (pfnGetModuleBaseName)GetProcAddress(hPsapi, "GetModuleBaseNameA");
|
||||
#endif
|
||||
GetModuleInformation = (pfnGetModuleInformation)GetProcAddress(hPsapi, "GetModuleInformation");
|
||||
if ((EnumProcessModules == NULL) || (GetModuleFileNameEx == NULL) || (GetModuleBaseName == NULL) || (GetModuleInformation == NULL))
|
||||
{
|
||||
FreeLibrary(hPsapi);
|
||||
return pHead;
|
||||
}
|
||||
|
||||
EnumProcessModules(m_hProcess, hMods, 0, &cbNeeded);
|
||||
hMods = new HMODULE[cbNeeded / sizeof(HMODULE)];
|
||||
ASSERT(NULL != hMods);
|
||||
ZeroMemory(hMods, cbNeeded);
|
||||
|
||||
if (!EnumProcessModules(m_hProcess, hMods, cbNeeded, &cbNeeded))
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (i = 0; i < cbNeeded / sizeof(HMODULE); i++)
|
||||
{
|
||||
GetModuleInformation(m_hProcess, hMods[i], &mi, sizeof(mi));
|
||||
GetModuleFileNameEx(m_hProcess, hMods[i], szModulePath, MAX_PATH);
|
||||
GetModuleBaseName(m_hProcess, hMods[i], szModuleName, MAX_MODULE_NAME32);
|
||||
LPMODULE_INFO pmi = new MODULE_INFO;
|
||||
ZeroMemory(pmi, sizeof(MODULE_INFO));
|
||||
pmi->dwModSize = mi.SizeOfImage;
|
||||
pmi->ModuleAddress = (DWORD64)mi.lpBaseOfDll;
|
||||
StringCchCopy(pmi->szModuleName, MAX_MODULE_NAME32, szModuleName);
|
||||
StringCchCopy(pmi->szModulePath, MAX_PATH, szModulePath);
|
||||
this->GetModuleInformation(pmi);
|
||||
if (pHead == NULL)
|
||||
{
|
||||
pHead = pmi;
|
||||
pTail = pHead;
|
||||
}else
|
||||
{
|
||||
pTail->pNext = pmi;
|
||||
pTail = pmi;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (hPsapi != NULL)
|
||||
{
|
||||
FreeLibrary(hPsapi);
|
||||
}
|
||||
if (hMods != NULL)
|
||||
{
|
||||
delete[] hMods;
|
||||
}
|
||||
|
||||
return pHead;
|
||||
}
|
||||
|
||||
void CStackWalker::OutputString(LPCTSTR lpszFormat, ...)
|
||||
{
|
||||
TCHAR szBuf[1024] = _T("");
|
||||
va_list args;
|
||||
va_start(args, lpszFormat);
|
||||
_vsntprintf_s(szBuf, 1024, lpszFormat, args);
|
||||
va_end(args);
|
||||
|
||||
OutputDebugString(szBuf);
|
||||
}
|
||||
|
||||
void CStackWalker::GetModuleInformation(LPMODULE_INFO pmi)
|
||||
{
|
||||
//USES_CONVERSION;
|
||||
IMAGEHLP_MODULE64 im = {0};
|
||||
im.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
|
||||
|
||||
VS_FIXEDFILEINFO* pvfi = NULL;
|
||||
DWORD dwHandle = 0;
|
||||
DWORD dwInfoSize = 0;
|
||||
dwInfoSize = GetFileVersionInfoSize(pmi->szModulePath, &dwHandle);
|
||||
|
||||
if (dwInfoSize > 0)
|
||||
{
|
||||
LPVOID lpData = new byte[dwInfoSize];
|
||||
ZeroMemory(lpData, dwInfoSize * sizeof(byte));
|
||||
|
||||
if (GetFileVersionInfo(pmi->szModulePath, dwHandle, dwInfoSize, lpData) > 0 )
|
||||
{
|
||||
TCHAR szBlock[] = _T("\\");
|
||||
UINT len;
|
||||
if (VerQueryValue(lpData, szBlock, (LPVOID*)&pvfi, &len))
|
||||
{
|
||||
WORD v1 = HIWORD(pvfi->dwFileVersionMS);
|
||||
WORD v2 = LOWORD(pvfi->dwFileVersionMS);
|
||||
WORD v3 = HIWORD(pvfi->dwFileVersionLS);
|
||||
WORD v4 = LOWORD(pvfi->dwFileVersionLS);
|
||||
_stprintf_s(pmi->szVersion, MAX_VERSION_LENGTH, _T("%d.%d.%d.%d"), v1, v2, v3, v4);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] lpData;
|
||||
}
|
||||
|
||||
SymGetModuleInfo64(m_hProcess, pmi->ModuleAddress, &im);
|
||||
StringCchCopy(pmi->szSymbolPath, MAX_PATH, textconv_helper::A2T_(im.LoadedPdbName));
|
||||
}
|
||||
|
||||
LPSTACKINFO CStackWalker::StackWalker(HANDLE hThread, const CONTEXT* context)
|
||||
{
|
||||
//USES_CONVERSION;
|
||||
//加载符号表
|
||||
LoadSymbol();
|
||||
|
||||
LPSTACKINFO pHead = NULL;
|
||||
LPSTACKINFO pTail = pHead;
|
||||
|
||||
//获取当前线程的上下文环境
|
||||
CONTEXT c = {0};
|
||||
if (context == NULL)
|
||||
{
|
||||
#if _WIN32_WINNT <= 0x0501
|
||||
if (hThread == GetCurrentThread())
|
||||
#else
|
||||
if (GetThreadId(hThread) == GetCurrentThreadId())
|
||||
#endif
|
||||
{
|
||||
GET_CURRENT_THREAD_CONTEXT(c, CONTEXT_FULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果不是当前线程,需要停止目标线程,以便取出正确的堆栈信息
|
||||
SuspendThread(hThread);
|
||||
memset(&c, 0, sizeof(CONTEXT));
|
||||
c.ContextFlags = CONTEXT_FULL;
|
||||
if (GetThreadContext(hThread, &c) == FALSE)
|
||||
{
|
||||
ResumeThread(hThread);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
c = *context;
|
||||
|
||||
STACKFRAME64 sf = {0};
|
||||
DWORD imageType;
|
||||
|
||||
//intel X86
|
||||
#ifdef _M_IX86
|
||||
imageType = IMAGE_FILE_MACHINE_I386;
|
||||
sf.AddrPC.Offset = c.Eip;
|
||||
sf.AddrPC.Mode = AddrModeFlat;
|
||||
sf.AddrFrame.Offset = c.Ebp;
|
||||
sf.AddrFrame.Mode = AddrModeFlat;
|
||||
sf.AddrStack.Offset = c.Esp;
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
// AMD
|
||||
#elif _M_X64
|
||||
imageType = IMAGE_FILE_MACHINE_AMD64;
|
||||
sf.AddrPC.Offset = c.Rip;
|
||||
sf.AddrPC.Mode = AddrModeFlat;
|
||||
sf.AddrFrame.Offset = c.Rsp;
|
||||
sf.AddrFrame.Mode = AddrModeFlat;
|
||||
sf.AddrStack.Offset = c.Rsp;
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
////intel Itanium(安腾)
|
||||
#elif _M_IA64
|
||||
imageType = IMAGE_FILE_MACHINE_IA64;
|
||||
sf.AddrPC.Offset = c.StIIP;
|
||||
sf.AddrPC.Mode = AddrModeFlat;
|
||||
sf.AddrFrame.Offset = c.IntSp;
|
||||
sf.AddrFrame.Mode = AddrModeFlat;
|
||||
sf.AddrBStore.Offset = c.RsBSP;
|
||||
sf.AddrBStore.Mode = AddrModeFlat;
|
||||
sf.AddrStack.Offset = c.IntSp;
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
#else
|
||||
#error "Platform not supported!"
|
||||
#endif
|
||||
|
||||
|
||||
DWORD64 dwDisplayment = 0;
|
||||
PIMAGEHLP_SYMBOL64 pSym = (PIMAGEHLP_SYMBOL64)new BYTE[sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN];
|
||||
PIMAGEHLP_LINE64 pLine = new IMAGEHLP_LINE64;
|
||||
while (StackWalk64(imageType, m_hProcess, hThread, &sf, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
|
||||
{
|
||||
ZeroMemory(pSym, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
|
||||
ZeroMemory(pLine, sizeof(IMAGEHLP_LINE64));
|
||||
|
||||
pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
||||
pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;
|
||||
pLine->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
|
||||
LPSTACKINFO pCallStack = new STACKINFO;
|
||||
ZeroMemory(pCallStack, sizeof(STACKINFO));
|
||||
pCallStack->szFncAddr = sf.AddrPC.Offset;
|
||||
if (sf.AddrPC.Offset != 0)
|
||||
{
|
||||
if(SymGetSymFromAddr64(m_hProcess, sf.AddrPC.Offset, &dwDisplayment, pSym))
|
||||
{
|
||||
char szName[STACKWALK_MAX_NAMELEN] = "";
|
||||
StringCchCopy(pCallStack->szFncName, STACKWALK_MAX_NAMELEN, textconv_helper::A2T_(pSym->Name));
|
||||
UnDecorateSymbolName(pSym->Name, szName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE);
|
||||
StringCchCopy(pCallStack->undFullName, STACKWALK_MAX_NAMELEN, textconv_helper::A2T_(szName));
|
||||
ZeroMemory(szName, STACKWALK_MAX_NAMELEN * sizeof(char));
|
||||
UnDecorateSymbolName(pSym->Name, szName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY);
|
||||
StringCchCopy(pCallStack->undName, STACKWALK_MAX_NAMELEN, textconv_helper::A2T_(szName));
|
||||
}else
|
||||
{
|
||||
//调用错误一般是487(地址无效或者没有访问的权限、在符号表中未找到指定地址的相关信息)
|
||||
//this->OutputString(_T("Call SymGetSymFromAddr64 ,Address %08x Error:%08x\n"), sf.AddrPC.Offset, GetLastError());
|
||||
|
||||
StringCchCopy(pCallStack->undFullName, STACKWALK_MAX_NAMELEN, textconv_helper::A2T_("Unknown"));
|
||||
}
|
||||
|
||||
if (SymGetLineFromAddr64(m_hProcess, sf.AddrPC.Offset, (DWORD*)&dwDisplayment, pLine))
|
||||
{
|
||||
StringCchCopy(pCallStack->szFileName, MAX_PATH, textconv_helper::A2T_(pLine->FileName));
|
||||
pCallStack->uFileNum = pLine->LineNumber;
|
||||
}else
|
||||
{
|
||||
//this->OutputString(_T("Call SymGetLineFromAddr64 ,Address %08x Error:%08x\n"), sf.AddrPC.Offset, GetLastError());
|
||||
|
||||
StringCchCopy(pCallStack->szFileName, MAX_PATH, textconv_helper::A2T_("Unknown file"));
|
||||
pCallStack->uFileNum = -1;
|
||||
}
|
||||
|
||||
//这里为了将获取函数信息失败的情况与正常的情况一起输出,防止用户在查看时出现误解
|
||||
this->OutputString(_T("%08llx:%s [%s][%ld]\n"), pCallStack->szFncAddr, pCallStack->undFullName, pCallStack->szFileName, pCallStack->uFileNum);
|
||||
if (NULL == pHead)
|
||||
{
|
||||
pHead = pCallStack;
|
||||
pTail = pHead;
|
||||
}else
|
||||
{
|
||||
pTail->pNext = pCallStack;
|
||||
pTail = pCallStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] pSym;
|
||||
delete pLine;
|
||||
|
||||
return pHead;
|
||||
}
|
||||
|
||||
void CStackWalker::FreeStackInformations(LPSTACKINFO psi)
|
||||
{
|
||||
LPSTACKINFO head = psi;
|
||||
while (NULL != head)
|
||||
{
|
||||
psi = psi->pNext;
|
||||
delete head;
|
||||
head = psi;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
#include <vector>
|
||||
|
||||
namespace textconv_helper
|
||||
{
|
||||
// Forward declarations of our classes. They are defined later.
|
||||
class CA2A_;
|
||||
class CA2W_;
|
||||
class CW2A_;
|
||||
class CW2W_;
|
||||
class CA2BSTR_;
|
||||
class CW2BSTR_;
|
||||
|
||||
// typedefs for the well known text conversions
|
||||
typedef CA2W_ A2W_;
|
||||
typedef CW2A_ W2A_;
|
||||
//typedef CW2BSTR_ W2BSTR_;
|
||||
//typedef CA2BSTR_ A2BSTR_;
|
||||
typedef CW2A_ BSTR2A_;
|
||||
typedef CW2W_ BSTR2W_;
|
||||
|
||||
#ifdef _UNICODE
|
||||
typedef CA2W_ A2T_;
|
||||
typedef CW2A_ T2A_;
|
||||
typedef CW2W_ T2W_;
|
||||
typedef CW2W_ W2T_;
|
||||
//typedef CW2BSTR_ T2BSTR_;
|
||||
//typedef BSTR2W_ BSTR2T_;
|
||||
#else
|
||||
typedef CA2A_ A2T_;
|
||||
typedef CA2A_ T2A_;
|
||||
typedef CA2W_ T2W_;
|
||||
typedef CW2A_ W2T_;
|
||||
typedef CA2BSTR_ T2BSTR_;
|
||||
typedef BSTR2A_ BSTR2T_;
|
||||
#endif
|
||||
|
||||
typedef A2W_ A2OLE_;
|
||||
typedef T2W_ T2OLE_;
|
||||
typedef CW2W_ W2OLE_;
|
||||
typedef W2A_ OLE2A_;
|
||||
typedef W2T_ OLE2T_;
|
||||
typedef CW2W_ OLE2W_;
|
||||
|
||||
class CA2W_
|
||||
{
|
||||
public:
|
||||
CA2W_(LPCSTR pStr, UINT codePage = CP_ACP) : m_pStr(pStr)
|
||||
{
|
||||
if (pStr)
|
||||
{
|
||||
// Resize the vector and assign null WCHAR to each element
|
||||
int length = MultiByteToWideChar(codePage, 0, pStr, -1, NULL, 0) + 1;
|
||||
m_vWideArray.assign(length, L'\0');
|
||||
|
||||
// Fill our vector with the converted WCHAR array
|
||||
MultiByteToWideChar(codePage, 0, pStr, -1, &m_vWideArray[0], length);
|
||||
}
|
||||
}
|
||||
~CA2W_() {}
|
||||
operator LPCWSTR() { return m_pStr ? &m_vWideArray[0] : NULL; }
|
||||
//operator LPOLESTR() { return m_pStr ? (LPOLESTR)&m_vWideArray[0] : (LPOLESTR)NULL; }
|
||||
|
||||
private:
|
||||
CA2W_(const CA2W_&);
|
||||
CA2W_& operator= (const CA2W_&);
|
||||
std::vector<wchar_t> m_vWideArray;
|
||||
LPCSTR m_pStr;
|
||||
};
|
||||
|
||||
class CW2A_
|
||||
{
|
||||
public:
|
||||
CW2A_(LPCWSTR pWStr, UINT codePage = CP_ACP) : m_pWStr(pWStr)
|
||||
// Usage:
|
||||
// CW2A_ ansiString(L"Some Text");
|
||||
// CW2A_ utf8String(L"Some Text", CP_UTF8);
|
||||
//
|
||||
// or
|
||||
// SetWindowTextA( W2A(L"Some Text") ); The ANSI version of SetWindowText
|
||||
{
|
||||
// Resize the vector and assign null char to each element
|
||||
int length = WideCharToMultiByte(codePage, 0, pWStr, -1, NULL, 0, NULL, NULL) + 1;
|
||||
m_vAnsiArray.assign(length, '\0');
|
||||
|
||||
// Fill our vector with the converted char array
|
||||
WideCharToMultiByte(codePage, 0, pWStr, -1, &m_vAnsiArray[0], length, NULL, NULL);
|
||||
}
|
||||
|
||||
~CW2A_()
|
||||
{
|
||||
m_pWStr = 0;
|
||||
}
|
||||
operator LPCSTR() { return m_pWStr ? &m_vAnsiArray[0] : NULL; }
|
||||
|
||||
private:
|
||||
CW2A_(const CW2A_&);
|
||||
CW2A_& operator= (const CW2A_&);
|
||||
std::vector<char> m_vAnsiArray;
|
||||
LPCWSTR m_pWStr;
|
||||
};
|
||||
|
||||
class CW2W_
|
||||
{
|
||||
public:
|
||||
CW2W_(LPCWSTR pWStr) : m_pWStr(pWStr) {}
|
||||
operator LPCWSTR() { return const_cast<LPWSTR>(m_pWStr); }
|
||||
//operator LPOLESTR() { return const_cast<LPOLESTR>(m_pWStr); }
|
||||
|
||||
private:
|
||||
CW2W_(const CW2W_&);
|
||||
CW2W_& operator= (const CW2W_&);
|
||||
|
||||
LPCWSTR m_pWStr;
|
||||
};
|
||||
|
||||
class CA2A_
|
||||
{
|
||||
public:
|
||||
CA2A_(LPCSTR pStr) : m_pStr(pStr) {}
|
||||
operator LPCSTR() { return (LPSTR)m_pStr; }
|
||||
|
||||
private:
|
||||
CA2A_(const CA2A_&);
|
||||
CA2A_& operator= (const CA2A_&);
|
||||
|
||||
LPCSTR m_pStr;
|
||||
};
|
||||
|
||||
/*class CW2BSTR_
|
||||
{
|
||||
public:
|
||||
CW2BSTR_(LPCWSTR pWStr) { m_bstrString = ::SysAllocString(pWStr); }
|
||||
~CW2BSTR_() { ::SysFreeString(m_bstrString); }
|
||||
operator BSTR() { return m_bstrString; }
|
||||
|
||||
private:
|
||||
CW2BSTR_(const CW2BSTR_&);
|
||||
CW2BSTR_& operator= (const CW2BSTR_&);
|
||||
BSTR m_bstrString;
|
||||
};
|
||||
|
||||
class CA2BSTR_
|
||||
{
|
||||
public:
|
||||
CA2BSTR_(LPCSTR pStr) { m_bstrString = ::SysAllocString(textconv_helper::CA2W_(pStr)); }
|
||||
~CA2BSTR_() { ::SysFreeString(m_bstrString); }
|
||||
operator BSTR() { return m_bstrString; }
|
||||
|
||||
private:
|
||||
CA2BSTR_(const CA2BSTR_&);
|
||||
CA2BSTR_& operator= (const CA2BSTR_&);
|
||||
BSTR m_bstrString;
|
||||
};*/
|
||||
}
|
||||
|
||||
#define MAX_SYMBOL_PATH 1024
|
||||
#define MAX_MODULE_NAME32 255
|
||||
#define TH32CS_SNAPMODULE 0x00000008
|
||||
#define MAX_VERSION_LENGTH 512
|
||||
#define STACKWALK_MAX_NAMELEN 1024
|
||||
|
||||
#define ASSERT(judge)\
|
||||
{\
|
||||
if(!(judge))\
|
||||
{\
|
||||
DebugBreak();\
|
||||
}\
|
||||
}
|
||||
|
||||
typedef struct tagMODULEENTRY32
|
||||
{
|
||||
DWORD dwSize;
|
||||
DWORD th32ModuleID; // This module
|
||||
DWORD th32ProcessID; // owning process
|
||||
DWORD GlblcntUsage; // Global usage count on the module
|
||||
DWORD ProccntUsage; // Module usage count in th32ProcessID's context
|
||||
BYTE* modBaseAddr; // Base address of module in th32ProcessID's context
|
||||
DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
|
||||
HMODULE hModule; // The hModule of this module in th32ProcessID's context
|
||||
TCHAR szModule[MAX_MODULE_NAME32 + 1];
|
||||
TCHAR szExePath[MAX_PATH];
|
||||
} MODULEENTRY32;
|
||||
|
||||
typedef struct _MODULEINFO
|
||||
{
|
||||
LPVOID lpBaseOfDll;
|
||||
DWORD SizeOfImage;
|
||||
LPVOID EntryPoint;
|
||||
} MODULEINFO, *LPMODULEINFO;
|
||||
|
||||
typedef MODULEENTRY32* PMODULEENTRY32;
|
||||
typedef MODULEENTRY32* LPMODULEENTRY32;
|
||||
|
||||
typedef struct _tag_MODULE_INFO
|
||||
{
|
||||
DWORD64 ModuleAddress;
|
||||
DWORD dwModSize;
|
||||
TCHAR szModuleName[MAX_MODULE_NAME32 + 1];
|
||||
TCHAR szModulePath[MAX_PATH];
|
||||
TCHAR szSymbolPath[MAX_PATH];
|
||||
TCHAR szVersion[MAX_VERSION_LENGTH];
|
||||
struct _tag_MODULE_INFO* pNext;
|
||||
}MODULE_INFO, *LPMODULE_INFO;
|
||||
|
||||
typedef struct tagSTACKINFO
|
||||
{
|
||||
DWORD64 szFncAddr;
|
||||
TCHAR szFileName[MAX_PATH];
|
||||
TCHAR szFncName[MAX_PATH];
|
||||
unsigned long uFileNum;
|
||||
TCHAR undName[STACKWALK_MAX_NAMELEN];
|
||||
TCHAR undFullName[STACKWALK_MAX_NAMELEN];
|
||||
tagSTACKINFO *pNext;
|
||||
}STACKINFO, *LPSTACKINFO;
|
||||
|
||||
class CStackWalker
|
||||
{
|
||||
public:
|
||||
CStackWalker(HANDLE hProcess = GetCurrentProcess(), WORD wPID = GetCurrentProcessId(), LPCTSTR lpSymbolPath = NULL);
|
||||
~CStackWalker(void);
|
||||
BOOL LoadSymbol();
|
||||
LPMODULE_INFO GetLoadModules();
|
||||
void GetModuleInformation(LPMODULE_INFO pmi);
|
||||
|
||||
void FreeModuleInformations(LPMODULE_INFO pmi);
|
||||
virtual void OutputString(LPCTSTR lpszFormat, ...);
|
||||
|
||||
LPSTACKINFO StackWalker(HANDLE hThread = GetCurrentThread(), const CONTEXT* context = NULL);
|
||||
void FreeStackInformations(LPSTACKINFO psi);
|
||||
|
||||
protected:
|
||||
LPMODULE_INFO GetModulesTH32();
|
||||
LPMODULE_INFO GetModulesPSAPI();
|
||||
protected:
|
||||
HANDLE m_hProcess;
|
||||
WORD m_wPID;
|
||||
LPTSTR m_lpszSymbolPath;
|
||||
BOOL m_bSymbolLoaded;
|
||||
};
|
||||
|
||||
#if defined(_M_IX86)
|
||||
#ifdef CURRENT_THREAD_VIA_EXCEPTION
|
||||
#define GET_CURRENT_THREAD_CONTEXT(c, contextFlags)\
|
||||
do\
|
||||
{\
|
||||
memset(&c, 0, sizeof(CONTEXT));\
|
||||
EXCEPTION_POINTERS* pExp = NULL;\
|
||||
__try\
|
||||
{\
|
||||
throw 0;\
|
||||
}\
|
||||
__except (((pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER:EXCEPTION_EXECUTE_HANDLER))\
|
||||
{\
|
||||
}\
|
||||
if (pExp != NULL)\
|
||||
memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT));\
|
||||
c.ContextFlags = contextFlags;\
|
||||
} while (0);
|
||||
#else
|
||||
#define GET_CURRENT_THREAD_CONTEXT(c, contextFlags) \
|
||||
do\
|
||||
{\
|
||||
memset(&c, 0, sizeof(CONTEXT));\
|
||||
c.ContextFlags = contextFlags;\
|
||||
__asm call $+5\
|
||||
__asm pop eax\
|
||||
__asm mov c.Eip, eax\
|
||||
__asm mov c.Ebp, ebp\
|
||||
__asm mov c.Esp, esp\
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define GET_CURRENT_THREAD_CONTEXT(c, contextFlags) \
|
||||
do\
|
||||
{ \
|
||||
memset(&c, 0, sizeof(CONTEXT));\
|
||||
c.ContextFlags = contextFlags;\
|
||||
RtlCaptureContext(&c);\
|
||||
} while (0);
|
||||
#endif
|
||||
@@ -0,0 +1,119 @@
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
/*
|
||||
* The utf8_check() function scans the '\0'-terminated string starting
|
||||
* at s. It returns a pointer to the first byte of the first malformed
|
||||
* or overlong UTF-8 sequence found, or NULL if the string contains
|
||||
* only correct UTF-8. It also spots UTF-8 sequences that could cause
|
||||
* trouble if converted to UTF-16, namely surrogate characters
|
||||
* (U+D800..U+DFFF) and non-Unicode positions (U+FFFE..U+FFFF). This
|
||||
* routine is very likely to find a malformed sequence if the input
|
||||
* uses any other encoding than UTF-8. It therefore can be used as a
|
||||
* very effective heuristic for distinguishing between UTF-8 and other
|
||||
* encodings.
|
||||
*
|
||||
* I wrote this code mainly as a specification of functionality; there
|
||||
* are no doubt performance optimizations possible for certain CPUs.
|
||||
*
|
||||
* Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> -- 2005-03-30
|
||||
* License: http://www.cl.cam.ac.uk/~mgk25/short-license.html
|
||||
*/
|
||||
|
||||
unsigned char *utf8_check(unsigned char *s)
|
||||
{
|
||||
while (*s) {
|
||||
if (*s < 0x80) {
|
||||
// 0xxxxxxx
|
||||
s++;
|
||||
} else if ((s[0] & 0xe0) == 0xc0) {
|
||||
// 110xxxxx 10xxxxxx
|
||||
if ((s[1] & 0xc0) != 0x80 ||
|
||||
(s[0] & 0xfe) == 0xc0) { // overlong?
|
||||
return s;
|
||||
} else {
|
||||
s += 2;
|
||||
}
|
||||
} else if ((s[0] & 0xf0) == 0xe0) {
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
if ((s[1] & 0xc0) != 0x80 ||
|
||||
(s[2] & 0xc0) != 0x80 ||
|
||||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || // overlong?
|
||||
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || // surrogate?
|
||||
(s[0] == 0xef && s[1] == 0xbf &&
|
||||
(s[2] & 0xfe) == 0xbe)) { // U+FFFE or U+FFFF?
|
||||
return s;
|
||||
} else {
|
||||
s += 3;
|
||||
}
|
||||
} else if ((s[0] & 0xf8) == 0xf0) {
|
||||
// 11110xxX 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if ((s[1] & 0xc0) != 0x80 ||
|
||||
(s[2] & 0xc0) != 0x80 ||
|
||||
(s[3] & 0xc0) != 0x80 ||
|
||||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || // overlong?
|
||||
(s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) { // > U+10FFFF?
|
||||
return s;
|
||||
} else {
|
||||
s += 4;
|
||||
}
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <program/library> <file>" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* target = argv[1];
|
||||
const char* filename = argv[2];
|
||||
|
||||
const auto error_exit = [=](const char* error) {
|
||||
std::cerr << "\n\tError: " << error << ": " << filename << "\n"
|
||||
<< "\tTarget: " << target << "\n"
|
||||
<< std::endl;
|
||||
std::exit(-2);
|
||||
};
|
||||
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
const auto size = file.tellg();
|
||||
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<char> buffer(size);
|
||||
|
||||
if (file.read(buffer.data(), size)) {
|
||||
buffer.push_back('\0');
|
||||
|
||||
// Check UTF-8 validity
|
||||
if (utf8_check(reinterpret_cast<unsigned char*>(buffer.data())) != nullptr) {
|
||||
error_exit("Source file does not contain (valid) UTF-8");
|
||||
}
|
||||
|
||||
// Check against a BOM mark
|
||||
if (buffer.size() >= 3
|
||||
&& buffer[0] == '\xef'
|
||||
&& buffer[1] == '\xbb'
|
||||
&& buffer[2] == '\xbf') {
|
||||
error_exit("Source file is valid UTF-8 but contains a BOM mark");
|
||||
}
|
||||
} else {
|
||||
error_exit("Could not read source file");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION @SLIC3R_VERSION@
|
||||
PRODUCTVERSION @SLIC3R_VERSION@
|
||||
{
|
||||
BLOCK "StringFileInfo"
|
||||
{
|
||||
BLOCK "040904E4"
|
||||
{
|
||||
VALUE "CompanyName", "SoftFever"
|
||||
VALUE "FileDescription", "@SLIC3R_APP_NAME@ G-code Viewer"
|
||||
VALUE "FileVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "ProductName", "@SLIC3R_APP_NAME@ G-code Viewer"
|
||||
VALUE "ProductVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "InternalName", "@SLIC3R_APP_NAME@ G-code Viewer"
|
||||
VALUE "LegalCopyright", ""
|
||||
VALUE "OriginalFilename", "bambu-gcodeviewer.exe"
|
||||
}
|
||||
}
|
||||
BLOCK "VarFileInfo"
|
||||
{
|
||||
VALUE "Translation", 0x409, 1252
|
||||
}
|
||||
}
|
||||
2 ICON "@SLIC3R_RESOURCES_DIR@/images/OrcaSlicer-gcodeviewer.ico"
|
||||
1 24 "OrcaSlicer.manifest"
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<assemblyIdentity version="@SLIC3R_VERSION@" name="Slic3r" type="Win32" />
|
||||
<description>Perl</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
|
||||
processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below indicates application support for Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
<!-- The ID below indicates application support for Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!-- The ID below indicates application support for Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- The ID below indicates application support for Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- The ID below indicates application support for Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- legacy -->
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,25 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION @SLIC3R_VERSION@
|
||||
PRODUCTVERSION @SLIC3R_VERSION@
|
||||
{
|
||||
BLOCK "StringFileInfo"
|
||||
{
|
||||
BLOCK "040904E4"
|
||||
{
|
||||
VALUE "CompanyName", "SoftFever"
|
||||
VALUE "FileDescription", "@SLIC3R_APP_NAME@"
|
||||
VALUE "FileVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "ProductName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "ProductVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "InternalName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "LegalCopyright", ""
|
||||
VALUE "OriginalFilename", "orca-slicer.exe"
|
||||
}
|
||||
}
|
||||
BLOCK "VarFileInfo"
|
||||
{
|
||||
VALUE "Translation", 0x409, 1252
|
||||
}
|
||||
}
|
||||
2 ICON "@SLIC3R_RESOURCES_DIR@/images/OrcaSlicer.ico"
|
||||
1 24 "OrcaSlicer.manifest"
|
||||
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>@SLIC3R_APP_KEY@</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>@SLIC3R_APP_NAME@ Copyright(C) 2026 OrcaSlicer Pte Ltd All Rights Reserved</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>images/OrcaSlicer.icns</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>@SLIC3R_APP_KEY@</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@SLIC3R_APP_NAME@ @SLIC3R_BUILD_ID@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.orcaslicer.OrcaSlicer</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@SLIC3R_BUILD_ID@</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<key>ATSApplicationFontsPath</key>
|
||||
<string>fonts/</string>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>orcasliceropen url</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>orcasliceropen</string>
|
||||
<string>orcaslicer</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>stl</string>
|
||||
<string>STL</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>images/stl.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>STL</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LISsAppleDefaultForType</key>
|
||||
<true/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>obj</string>
|
||||
<string>OBJ</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>images/OrcaSlicer.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>STL</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LISsAppleDefaultForType</key>
|
||||
<true/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>amf</string>
|
||||
<string>AMF</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>images/OrcaSlicer.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>AMF</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LISsAppleDefaultForType</key>
|
||||
<true/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>3mf</string>
|
||||
<string>3MF</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>images/OrcaSlicer.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>3MF</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LISsAppleDefaultForType</key>
|
||||
<true/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>gcode</string>
|
||||
<string>GCODE</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>images/gcode.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>GCODE</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LISsAppleDefaultForType</key>
|
||||
<true/>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>ASAN_OPTIONS</key>
|
||||
<string>detect_container_overflow=0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- for dynamic loading of libraries without signature validation. Used for 3dconnection drivers.-->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
APPIMAGETOOLURL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage"
|
||||
|
||||
|
||||
APP_IMAGE="@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage"
|
||||
|
||||
wget ${APPIMAGETOOLURL} -O ../appimagetool.AppImage
|
||||
chmod +x ../appimagetool.AppImage
|
||||
|
||||
if [ -f /.dockerenv ] ; then # Only run if inside of a Docker Container
|
||||
sed '0,/AI\x02/{s|AI\x02|\x00\x00\x00|}' -i ../appimagetool.AppImage
|
||||
fi
|
||||
|
||||
mv @SLIC3R_APP_CMD@ AppRun
|
||||
chmod +x AppRun
|
||||
|
||||
cp resources/images/@SLIC3R_APP_KEY@_192px.png @SLIC3R_APP_KEY@.png
|
||||
mkdir -p usr/share/icons/hicolor/192x192/apps
|
||||
cp resources/images/@SLIC3R_APP_KEY@_192px.png usr/share/icons/hicolor/192x192/apps/@SLIC3R_APP_KEY@.png
|
||||
cat <<EOF > com.orcaslicer.@SLIC3R_APP_KEY@.desktop
|
||||
[Desktop Entry]
|
||||
Name=@SLIC3R_APP_KEY@
|
||||
Exec=AppRun %F
|
||||
Icon=@SLIC3R_APP_KEY@
|
||||
Type=Application
|
||||
PrefersNonDefaultGPU=true
|
||||
X-KDE-RunOnDiscreteGpu=true
|
||||
Categories=Utility;
|
||||
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;
|
||||
EOF
|
||||
|
||||
mkdir -p usr/share/applications
|
||||
cp com.orcaslicer.@SLIC3R_APP_KEY@.desktop usr/share/applications/
|
||||
|
||||
mkdir -p usr/share/metainfo
|
||||
cp @CMAKE_CURRENT_SOURCE_DIR@/scripts/flatpak/com.orcaslicer.@SLIC3R_APP_KEY@.metainfo.xml usr/share/metainfo/
|
||||
|
||||
|
||||
export ARCH=$(uname -m)
|
||||
|
||||
if [ -f /.dockerenv ] ; then # Only run if inside of a Docker Container
|
||||
../appimagetool.AppImage --appimage-extract-and-run . $([ -n "${container}" ] && echo '--appimage-extract-and-run')
|
||||
else
|
||||
../appimagetool.AppImage . $([ -n "${container}" ] && echo '--appimage-extract-and-run')
|
||||
fi
|
||||
|
||||
mv @SLIC3R_APP_KEY@-$(uname -m).AppImage ${APP_IMAGE}
|
||||
chmod +x ${APP_IMAGE}
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Despite the script name, this doesn't necessarily create an "image";
|
||||
# it sets up a compatibility script wrapper for the binary and arranges some resources.
|
||||
|
||||
set -e
|
||||
|
||||
export ROOT=$(echo $ROOT | grep . || pwd)
|
||||
export NCORES=$(nproc --all)
|
||||
CONFIG=Release
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
||||
POLICY_FILE="${REPO_ROOT}/scripts/appimage_lib_policy.sh"
|
||||
CHECK_SCRIPT="${REPO_ROOT}/scripts/check_appimage_libs.sh"
|
||||
|
||||
if [ ! -f "${POLICY_FILE}" ]; then
|
||||
echo "Error: missing AppImage helper ${POLICY_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "${POLICY_FILE}"
|
||||
|
||||
while getopts ":ihR:" opt; do
|
||||
case ${opt} in
|
||||
i )
|
||||
export BUILD_IMAGE="1"
|
||||
;;
|
||||
h ) echo "Usage: ./build_linux_image.sh [-i][-R config]"
|
||||
echo " -i: Generate Appimage (optional)"
|
||||
echo " -R: Specify from which config to obtain the binary: Release, RelWithDebInfo, or Debug"
|
||||
exit 1
|
||||
;;
|
||||
R )
|
||||
CONFIG=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
copy_directory_if_present() {
|
||||
local src_dir="$1"
|
||||
local dst_dir="$2"
|
||||
|
||||
if [ -n "$src_dir" ] && [ -d "$src_dir" ]; then
|
||||
mkdir -p "$dst_dir"
|
||||
cp -a "$src_dir"/. "$dst_dir"/
|
||||
fi
|
||||
}
|
||||
|
||||
find_pkg_config_dir() {
|
||||
local variable="$1"
|
||||
shift
|
||||
|
||||
local module
|
||||
for module in "$@"; do
|
||||
if pkg-config --exists "$module" 2>/dev/null; then
|
||||
pkg-config --variable="$variable" "$module" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
find_first_existing_file() {
|
||||
local path
|
||||
for path in "$@"; do
|
||||
if [ -f "$path" ]; then
|
||||
printf '%s\n' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
copy_shared_object_to_dir() {
|
||||
local src="$1"
|
||||
local dst_dir="$2"
|
||||
local src_real dst_name soname
|
||||
|
||||
src_real="$(readlink -f "$src")"
|
||||
dst_name="$(basename "$src_real")"
|
||||
mkdir -p "$dst_dir"
|
||||
cp -fL "$src_real" "$dst_dir/$dst_name"
|
||||
|
||||
if [ -L "$src" ]; then
|
||||
ln -snf "$dst_name" "$dst_dir/$(basename "$src")"
|
||||
fi
|
||||
|
||||
soname="$(objdump -p "$src_real" 2>/dev/null | awk '$1 == "SONAME" { print $2; exit }')"
|
||||
if [ -n "$soname" ] && [ "$soname" != "$dst_name" ]; then
|
||||
ln -snf "$dst_name" "$dst_dir/$soname"
|
||||
fi
|
||||
}
|
||||
|
||||
bundle_dependency_closure() {
|
||||
local dst_dir="$1"
|
||||
shift
|
||||
|
||||
local -a queue=("$@")
|
||||
local target dep dep_real copied_path
|
||||
declare -A seen=()
|
||||
|
||||
while [ ${#queue[@]} -gt 0 ]; do
|
||||
target="${queue[0]}"
|
||||
queue=("${queue[@]:1}")
|
||||
|
||||
if [ ! -e "$target" ] || ! appimage_is_elf_file "$target"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
while IFS= read -r dep; do
|
||||
if [[ "$dep" == MISSING:* ]]; then
|
||||
echo "Error: missing runtime dependency ${dep#MISSING:} while bundling $target"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dep_real="$(readlink -f "$dep" 2>/dev/null || printf '%s' "$dep")"
|
||||
if appimage_is_host_library "$dep_real"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "${seen[$dep_real]}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
seen[$dep_real]=1
|
||||
copy_shared_object_to_dir "$dep" "$dst_dir"
|
||||
queue+=("$dep_real")
|
||||
done < <(appimage_list_direct_dependencies "$target")
|
||||
done
|
||||
}
|
||||
|
||||
echo -n "[9/9] Generating Linux app..."
|
||||
# {
|
||||
if [ -d "package" ]; then
|
||||
rm -rf package
|
||||
fi
|
||||
|
||||
APPDIR="package"
|
||||
BIN_DIR="$APPDIR/bin"
|
||||
LIB_DIR="$APPDIR/lib"
|
||||
PRIVATE_LIB_DIR="$LIB_DIR/orca-runtime"
|
||||
SHARE_DIR="$APPDIR/share"
|
||||
LIBEXEC_DIR="$APPDIR/libexec"
|
||||
BUNDLE_DESKTOP_STACK="${ORCA_BUNDLE_DESKTOP_STACK:-0}"
|
||||
GST_PLUGIN_DIR="$LIB_DIR/gstreamer-1.0"
|
||||
GIO_MODULE_DIR="$LIB_DIR/gio/modules"
|
||||
GDK_PIXBUF_DIR="$LIB_DIR/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
|
||||
mkdir -p "$BIN_DIR" "$LIB_DIR" "$PRIVATE_LIB_DIR" "$SHARE_DIR" "$LIBEXEC_DIR"
|
||||
if [ "$BUNDLE_DESKTOP_STACK" = "1" ]; then
|
||||
mkdir -p "$GST_PLUGIN_DIR" "$GIO_MODULE_DIR" "$GDK_PIXBUF_DIR"
|
||||
fi
|
||||
|
||||
cp -Rf ../resources "$APPDIR/resources"
|
||||
|
||||
ORIGINAL_BINARY_LOCATION=""
|
||||
if [ -f "src/${CONFIG}/@SLIC3R_APP_CMD@" ]; then
|
||||
ORIGINAL_BINARY_LOCATION="$(realpath "src/${CONFIG}/@SLIC3R_APP_CMD@")"
|
||||
elif [ -f "src/@SLIC3R_APP_CMD@" ]; then
|
||||
ORIGINAL_BINARY_LOCATION="$(realpath "src/@SLIC3R_APP_CMD@")"
|
||||
else
|
||||
echo "Error: @SLIC3R_APP_CMD@ binary not found in any configuration directory"
|
||||
exit 1
|
||||
fi
|
||||
cp -fl "${ORIGINAL_BINARY_LOCATION}" "$BIN_DIR/@SLIC3R_APP_CMD@"
|
||||
|
||||
if [ "$BUNDLE_DESKTOP_STACK" = "1" ]; then
|
||||
GSTREAMER_PLUGIN_SOURCE_DIR="$(find_pkg_config_dir pluginsdir gstreamer-1.0 || true)"
|
||||
if [ -z "$GSTREAMER_PLUGIN_SOURCE_DIR" ]; then
|
||||
GSTREAMER_PLUGIN_SOURCE_DIR="$(find /usr/lib /usr/lib64 -maxdepth 4 -type d -name gstreamer-1.0 2>/dev/null | head -n1)"
|
||||
fi
|
||||
copy_directory_if_present "$GSTREAMER_PLUGIN_SOURCE_DIR" "$GST_PLUGIN_DIR"
|
||||
|
||||
GIO_MODULE_SOURCE_DIR="$(find_pkg_config_dir giomoduledir gio-2.0 || true)"
|
||||
if [ -z "$GIO_MODULE_SOURCE_DIR" ]; then
|
||||
GIO_MODULE_SOURCE_DIR="$(find /usr/lib /usr/lib64 -maxdepth 5 -type d -path '*/gio/modules' 2>/dev/null | head -n1)"
|
||||
fi
|
||||
copy_directory_if_present "$GIO_MODULE_SOURCE_DIR" "$GIO_MODULE_DIR"
|
||||
|
||||
GDK_PIXBUF_SOURCE_DIR="$(find_pkg_config_dir gdk_pixbuf_moduledir gdk-pixbuf-2.0 || true)"
|
||||
if [ -z "$GDK_PIXBUF_SOURCE_DIR" ]; then
|
||||
GDK_PIXBUF_SOURCE_DIR="$(find /usr/lib /usr/lib64 -maxdepth 6 -type d -path '*/gdk-pixbuf-2.0/*/loaders' 2>/dev/null | head -n1)"
|
||||
fi
|
||||
copy_directory_if_present "$GDK_PIXBUF_SOURCE_DIR" "$GDK_PIXBUF_DIR"
|
||||
|
||||
GLIB_SCHEMAS_SOURCE_DIR="$(find_pkg_config_dir schemasdir gio-2.0 || true)"
|
||||
if [ -z "$GLIB_SCHEMAS_SOURCE_DIR" ]; then
|
||||
GLIB_SCHEMAS_SOURCE_DIR="/usr/share/glib-2.0/schemas"
|
||||
fi
|
||||
copy_directory_if_present "$GLIB_SCHEMAS_SOURCE_DIR" "$SHARE_DIR/glib-2.0/schemas"
|
||||
if [ -d "$SHARE_DIR/glib-2.0/schemas" ] && command -v glib-compile-schemas >/dev/null 2>&1; then
|
||||
glib-compile-schemas "$SHARE_DIR/glib-2.0/schemas"
|
||||
fi
|
||||
|
||||
# Distros disagree on where helper executables live: pkg-config may point to libexec,
|
||||
# Debian/Ubuntu often ship the scanner under a multiarch libdir, and RPM distros may use lib64.
|
||||
GST_PLUGIN_SCANNER_SOURCE="$(find_first_existing_file \
|
||||
"$(find_pkg_config_dir pluginscannerdir gstreamer-1.0 || true)/gst-plugin-scanner" \
|
||||
/usr/libexec/gstreamer-1.0/gst-plugin-scanner \
|
||||
/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner \
|
||||
/usr/lib/*/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner \
|
||||
/usr/lib64/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner)" || true
|
||||
if [ -n "$GST_PLUGIN_SCANNER_SOURCE" ]; then
|
||||
mkdir -p "$LIBEXEC_DIR/gstreamer-1.0"
|
||||
cp -fL "$GST_PLUGIN_SCANNER_SOURCE" "$LIBEXEC_DIR/gstreamer-1.0/gst-plugin-scanner"
|
||||
fi
|
||||
|
||||
# Prefer the canonical pkg-config-reported helper path, but keep PATH-based fallbacks
|
||||
# for distros exposing gdk-pixbuf-query-loaders or gdk-pixbuf-query-loaders-64 directly.
|
||||
GDK_PIXBUF_QUERY_LOADERS_SOURCE="$(find_first_existing_file \
|
||||
"$(pkg-config --variable=gdk_pixbuf_query_loaders gdk-pixbuf-2.0 2>/dev/null || true)" \
|
||||
"$(command -v gdk-pixbuf-query-loaders 2>/dev/null || true)" \
|
||||
"$(command -v gdk-pixbuf-query-loaders-64 2>/dev/null || true)")" || true
|
||||
if [ -n "$GDK_PIXBUF_QUERY_LOADERS_SOURCE" ]; then
|
||||
cp -fL "$GDK_PIXBUF_QUERY_LOADERS_SOURCE" "$LIBEXEC_DIR/gdk-pixbuf-query-loaders"
|
||||
fi
|
||||
fi
|
||||
|
||||
# WebKitGTK helper binaries are resolved from distro-specific absolute paths
|
||||
# baked into libwebkit2gtk. Bundling Ubuntu's WebKitGTK inside an AppImage
|
||||
# built with -g makes those helper paths unusable on Fedora and vice versa,
|
||||
# so rely on the host WebKitGTK runtime instead of copying the helper stack.
|
||||
|
||||
mapfile -d '' BUNDLE_TARGETS < <(find "$BIN_DIR" "$LIB_DIR" "$LIBEXEC_DIR" -type f -print0 2>/dev/null)
|
||||
bundle_dependency_closure "$PRIVATE_LIB_DIR" "${BUNDLE_TARGETS[@]}"
|
||||
|
||||
cat << EOF >"$LIBEXEC_DIR/@SLIC3R_APP_CMD@-env"
|
||||
#!/bin/sh
|
||||
set -e
|
||||
SELF_DIR=\$(CDPATH= cd -- "\$(dirname -- "\$0")" && pwd)
|
||||
APPDIR="\${APPDIR:-\$(CDPATH= cd -- "\$SELF_DIR/.." && pwd)}"
|
||||
USE_BUNDLED_WEBKITGTK_STACK=0
|
||||
if [ -e "\$APPDIR/lib/libwebkit2gtk-4.1.so.0" ] || [ -e "\$APPDIR/lib/libwebkit2gtk-4.0.so.0" ]; then
|
||||
USE_BUNDLED_WEBKITGTK_STACK=1
|
||||
fi
|
||||
|
||||
export APPDIR
|
||||
PRIVATE_LIB_DIR="\$APPDIR/lib/orca-runtime"
|
||||
if [ -d "\$PRIVATE_LIB_DIR" ]; then
|
||||
export LD_LIBRARY_PATH="\$PRIVATE_LIB_DIR:\$APPDIR/bin\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
else
|
||||
export LD_LIBRARY_PATH="\$APPDIR/bin\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
fi
|
||||
|
||||
has_host_runtime_library() {
|
||||
local lib_name path
|
||||
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
if ldconfig -p 2>/dev/null | grep -Fq " \$lib_name"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
for path in \
|
||||
/lib /lib64 /usr/lib /usr/lib64 \
|
||||
/usr/lib/* /usr/lib64/* /lib/* /lib64/*; do
|
||||
if [ -e "\$path/\$lib_name" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
target_missing_runtime_library() {
|
||||
local target_bin="\$1"
|
||||
local lib_name="\$2"
|
||||
|
||||
if [ -z "\$target_bin" ] || [ ! -e "\$target_bin" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v ldd >/dev/null 2>&1; then
|
||||
if ldd "\$target_bin" 2>/dev/null | grep -Fq "\$lib_name => not found"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
TARGET_BIN="\$1"
|
||||
|
||||
if target_missing_runtime_library "\$TARGET_BIN" "libOpenGL.so.0" || ! has_host_runtime_library "libOpenGL.so.0"; then
|
||||
echo "Error: missing host OpenGL runtime library libOpenGL.so.0." >&2
|
||||
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0 and libglu1-mesa" >&2
|
||||
echo "On Arch/CachyOS, install: libglvnd" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "\$USE_BUNDLED_WEBKITGTK_STACK" = "1" ]; then
|
||||
export LD_LIBRARY_PATH="\$APPDIR/lib\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
export GST_PLUGIN_SYSTEM_PATH=""
|
||||
export GST_PLUGIN_PATH="\$APPDIR/lib/gstreamer-1.0\${GST_PLUGIN_PATH:+:\$GST_PLUGIN_PATH}"
|
||||
export GIO_EXTRA_MODULES="\$APPDIR/lib/gio/modules\${GIO_EXTRA_MODULES:+:\$GIO_EXTRA_MODULES}"
|
||||
export XDG_DATA_DIRS="\$APPDIR/share\${XDG_DATA_DIRS:+:\$XDG_DATA_DIRS}"
|
||||
|
||||
if [ -d "\$APPDIR/share/glib-2.0/schemas" ]; then
|
||||
export GSETTINGS_SCHEMA_DIR="\$APPDIR/share/glib-2.0/schemas"
|
||||
fi
|
||||
|
||||
if [ -x "\$APPDIR/libexec/gstreamer-1.0/gst-plugin-scanner" ]; then
|
||||
export GST_PLUGIN_SCANNER="\$APPDIR/libexec/gstreamer-1.0/gst-plugin-scanner"
|
||||
fi
|
||||
|
||||
if [ -d "\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders" ]; then
|
||||
export GDK_PIXBUF_MODULEDIR="\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
if [ -x "\$APPDIR/libexec/gdk-pixbuf-query-loaders" ]; then
|
||||
PIXBUF_CACHE_DIR="\${XDG_CACHE_HOME:-\$HOME/.cache}/@SLIC3R_APP_KEY@"
|
||||
PIXBUF_CACHE_FILE="\$PIXBUF_CACHE_DIR/gdk-pixbuf-loaders.cache"
|
||||
mkdir -p "\$PIXBUF_CACHE_DIR"
|
||||
if [ ! -s "\$PIXBUF_CACHE_FILE" ] || find "\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders" -type f -newer "\$PIXBUF_CACHE_FILE" | grep -q .; then
|
||||
"\$APPDIR/libexec/gdk-pixbuf-query-loaders" "\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders"/*.so > "\$PIXBUF_CACHE_FILE" 2>/dev/null || true
|
||||
fi
|
||||
if [ -s "\$PIXBUF_CACHE_FILE" ]; then
|
||||
export GDK_PIXBUF_MODULE_FILE="\$PIXBUF_CACHE_FILE"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "\$APPDIR/lib/webkit2gtk-4.1" ]; then
|
||||
export WEBKIT_EXEC_PATH="\$APPDIR/lib/webkit2gtk-4.1"
|
||||
elif [ -d "\$APPDIR/lib/webkit2gtk-4.0" ]; then
|
||||
export WEBKIT_EXEC_PATH="\$APPDIR/lib/webkit2gtk-4.0"
|
||||
fi
|
||||
|
||||
if [ -d "\$APPDIR/lib/webkit2gtk-4.1/injected-bundle" ]; then
|
||||
export WEBKIT_INJECTED_BUNDLE_PATH="\$APPDIR/lib/webkit2gtk-4.1/injected-bundle"
|
||||
elif [ -d "\$APPDIR/lib/webkit2gtk-4.0/injected-bundle" ]; then
|
||||
export WEBKIT_INJECTED_BUNDLE_PATH="\$APPDIR/lib/webkit2gtk-4.0/injected-bundle"
|
||||
fi
|
||||
else
|
||||
if target_missing_runtime_library "\$TARGET_BIN" "libwebkit2gtk-4.1.so.0" || \
|
||||
target_missing_runtime_library "\$TARGET_BIN" "libjavascriptcoregtk-4.1.so.0" || \
|
||||
! has_host_runtime_library "libwebkit2gtk-4.1.so.0" || \
|
||||
! has_host_runtime_library "libjavascriptcoregtk-4.1.so.0"; then
|
||||
echo "Error: missing host WebKitGTK 4.1 runtime libraries." >&2
|
||||
echo "Install the distro package providing libwebkit2gtk-4.1.so.0 and libjavascriptcoregtk-4.1.so.0." >&2
|
||||
echo "On Arch/CachyOS, install: webkit2gtk-4.1" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "\$@"
|
||||
EOF
|
||||
chmod ug+x "$LIBEXEC_DIR/@SLIC3R_APP_CMD@-env"
|
||||
|
||||
cat << EOF >@SLIC3R_APP_CMD@
|
||||
#!/bin/bash
|
||||
DIR=\$(dirname "\$(readlink -f "\$0")")
|
||||
export APPDIR="\${APPDIR:-\$DIR}"
|
||||
|
||||
# FIXME: OrcaSlicer segfault workarounds
|
||||
# 1) OrcaSlicer will segfault on systems where locale info is not as expected (i.e. Holo-ISO arch-based distro)
|
||||
export LC_ALL=C
|
||||
|
||||
if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ]; then
|
||||
if command -v glxinfo >/dev/null 2>&1; then
|
||||
RENDERER=\$(glxinfo | grep "OpenGL renderer string:" | sed 's/.*: //')
|
||||
if echo "\$RENDERER" | grep -qi "NVIDIA"; then
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
DRIVER_VERSION=\$(nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -n1)
|
||||
DRIVER_MAJOR=\$(echo "\$DRIVER_VERSION" | cut -d. -f1)
|
||||
[ "\$DRIVER_MAJOR" -gt 555 ] && ZINK_FORCE_OVERRIDE=1
|
||||
fi
|
||||
if [ "\$ZINK_FORCE_OVERRIDE" = "1" ]; then
|
||||
export __GLX_VENDOR_LIBRARY_NAME=mesa
|
||||
export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
|
||||
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
||||
export GALLIUM_DRIVER=zink
|
||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
exec "\$DIR/libexec/@SLIC3R_APP_CMD@-env" "\$DIR/bin/@SLIC3R_APP_CMD@" "\$@"
|
||||
EOF
|
||||
|
||||
chmod ug+x @SLIC3R_APP_CMD@
|
||||
cp -fl @SLIC3R_APP_CMD@ "$APPDIR/@SLIC3R_APP_CMD@"
|
||||
|
||||
if [ -x "${CHECK_SCRIPT}" ] && [ -z "${ORCA_SKIP_APPIMAGE_AUDIT}" ]; then
|
||||
"${CHECK_SCRIPT}" "$APPDIR" "$BIN_DIR/@SLIC3R_APP_CMD@"
|
||||
fi
|
||||
# } &> $ROOT/Build.log # Capture all command output
|
||||
echo "done"
|
||||
|
||||
if [[ -n "$BUILD_IMAGE" ]]
|
||||
then
|
||||
echo -n "Creating Appimage for distribution..."
|
||||
# {
|
||||
rm -rf package_appimage
|
||||
cp -Rf package package_appimage
|
||||
pushd package_appimage > /dev/null
|
||||
chmod +x ../build_appimage.sh
|
||||
if ../build_appimage.sh; then
|
||||
popd > /dev/null
|
||||
mv package_appimage/"@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage" "@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage"
|
||||
rm -fR package_appimage
|
||||
else
|
||||
popd > /dev/null
|
||||
fi
|
||||
# } &> $ROOT/Build.log # Capture all command output
|
||||
echo "done"
|
||||
fi
|
||||
@@ -0,0 +1,14 @@
|
||||
[Desktop Entry]
|
||||
Name=OrcaSlicer
|
||||
GenericName=3D Printing Software
|
||||
Icon=OrcaSlicer
|
||||
Exec=orca-slicer %U
|
||||
Terminal=false
|
||||
Type=Application
|
||||
PrefersNonDefaultGPU=true
|
||||
X-KDE-RunOnDiscreteGpu=true
|
||||
MimeType=model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;x-scheme-handler/orcaslicer;model/step;
|
||||
Categories=Graphics;3DGraphics;Engineering;
|
||||
Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA
|
||||
StartupNotify=false
|
||||
StartupWMClass=orca-slicer
|
||||
@@ -0,0 +1,2 @@
|
||||
#cmakedefine SLIC3R_FHS @SLIC3R_FHS@
|
||||
#define SLIC3R_FHS_RESOURCES "@SLIC3R_FHS_RESOURCES@"
|
||||
@@ -0,0 +1,3 @@
|
||||
add_library(glad STATIC src/gl.c)
|
||||
target_include_directories(glad PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_link_libraries(glad PRIVATE ${CMAKE_DL_LIBS})
|
||||
@@ -0,0 +1,311 @@
|
||||
#ifndef __khrplatform_h_
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
** "Materials"), to deal in the Materials without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
||||
** permit persons to whom the Materials are furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included
|
||||
** in all copies or substantial portions of the Materials.
|
||||
**
|
||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
*/
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
* should be located on your system and for more details of its use:
|
||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||
*
|
||||
* This file should be included as
|
||||
* #include <KHR/khrplatform.h>
|
||||
* by Khronos client API header files that use its types and defines.
|
||||
*
|
||||
* The types in khrplatform.h should only be used to define API-specific types.
|
||||
*
|
||||
* Types defined in khrplatform.h:
|
||||
* khronos_int8_t signed 8 bit
|
||||
* khronos_uint8_t unsigned 8 bit
|
||||
* khronos_int16_t signed 16 bit
|
||||
* khronos_uint16_t unsigned 16 bit
|
||||
* khronos_int32_t signed 32 bit
|
||||
* khronos_uint32_t unsigned 32 bit
|
||||
* khronos_int64_t signed 64 bit
|
||||
* khronos_uint64_t unsigned 64 bit
|
||||
* khronos_intptr_t signed same number of bits as a pointer
|
||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
||||
* khronos_ssize_t signed size
|
||||
* khronos_usize_t unsigned size
|
||||
* khronos_float_t signed 32 bit floating point
|
||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
||||
* nanoseconds
|
||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
||||
* only be used as a base type when a client API's boolean type is
|
||||
* an enum. Client APIs which use an integer or other type for
|
||||
* booleans cannot use this as the base type for their boolean.
|
||||
*
|
||||
* Tokens defined in khrplatform.h:
|
||||
*
|
||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
||||
*
|
||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
||||
*
|
||||
* Calling convention macros defined in this file:
|
||||
* KHRONOS_APICALL
|
||||
* KHRONOS_APIENTRY
|
||||
* KHRONOS_APIATTRIBUTES
|
||||
*
|
||||
* These may be used in function prototypes as:
|
||||
*
|
||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
||||
* int arg1,
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||
# define KHRONOS_STATIC 1
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
* This precedes the return type of the function in the function prototype.
|
||||
*/
|
||||
#if defined(KHRONOS_STATIC)
|
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */
|
||||
# define KHRONOS_APICALL
|
||||
#elif defined(_WIN32)
|
||||
# define KHRONOS_APICALL __declspec(dllimport)
|
||||
#elif defined (__SYMBIAN32__)
|
||||
# define KHRONOS_APICALL IMPORT_C
|
||||
#elif defined(__ANDROID__)
|
||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
||||
#else
|
||||
# define KHRONOS_APICALL
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the return type of the function and precedes the function
|
||||
* name in the function prototype.
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
||||
/* Win32 but not WinCE */
|
||||
# define KHRONOS_APIENTRY __stdcall
|
||||
#else
|
||||
# define KHRONOS_APIENTRY
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the closing parenthesis of the function prototype arguments.
|
||||
*/
|
||||
#if defined (__ARMCC_2__)
|
||||
#define KHRONOS_APIATTRIBUTES __softfp
|
||||
#else
|
||||
#define KHRONOS_APIATTRIBUTES
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions
|
||||
*-----------------------------------------------------------------------*/
|
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h>
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
/*
|
||||
* To support platform where unsigned long cannot be used interchangeably with
|
||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||
* unsigned long long or similar (this results in different C++ name mangling).
|
||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||
* platforms where the size of a pointer is larger than the size of long.
|
||||
*/
|
||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||
#define KHRONOS_USE_INTPTR_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
/*
|
||||
* Using <inttypes.h>
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
|
||||
/*
|
||||
* Win32
|
||||
*/
|
||||
typedef __int32 khronos_int32_t;
|
||||
typedef unsigned __int32 khronos_uint32_t;
|
||||
typedef __int64 khronos_int64_t;
|
||||
typedef unsigned __int64 khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
|
||||
/*
|
||||
* Sun or Digital
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int khronos_int64_t;
|
||||
typedef unsigned long int khronos_uint64_t;
|
||||
#else
|
||||
typedef long long int khronos_int64_t;
|
||||
typedef unsigned long long int khronos_uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif 0
|
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#define KHRONOS_SUPPORT_INT64 0
|
||||
#define KHRONOS_SUPPORT_FLOAT 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Generic fallback
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms
|
||||
*/
|
||||
typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef KHRONOS_USE_INTPTR_T
|
||||
typedef intptr_t khronos_intptr_t;
|
||||
typedef uintptr_t khronos_uintptr_t;
|
||||
#elif defined(_WIN64)
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN64)
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT
|
||||
/*
|
||||
* Float type
|
||||
*/
|
||||
typedef float khronos_float_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_INT64
|
||||
/* Time types
|
||||
*
|
||||
* These types can be used to represent a time interval in nanoseconds or
|
||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
||||
* time the system booted). The Unadjusted System Time is an unsigned
|
||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
||||
* may be either signed or unsigned.
|
||||
*/
|
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits.
|
||||
*/
|
||||
#ifndef KHRONOS_MAX_ENUM
|
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enumerated boolean type
|
||||
*
|
||||
* Values other than zero should be considered to be true. Therefore
|
||||
* comparisons should not be made against KHRONOS_TRUE.
|
||||
*/
|
||||
typedef enum {
|
||||
KHRONOS_FALSE = 0,
|
||||
KHRONOS_TRUE = 1,
|
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||
} khronos_boolean_enum_t;
|
||||
|
||||
#endif /* __khrplatform_h_ */
|
||||
File diff suppressed because it is too large
Load Diff
+2639
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,322 @@
|
||||
#include "AABBMesh.hpp"
|
||||
#include <Execution/ExecutionTBB.hpp>
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AABBMesh::AABBImpl {
|
||||
private:
|
||||
AABBTreeIndirect::Tree3f m_tree;
|
||||
double m_triangle_ray_epsilon;
|
||||
|
||||
public:
|
||||
void init(const indexed_triangle_set &its, bool calculate_epsilon)
|
||||
{
|
||||
m_triangle_ray_epsilon = 0.000001;
|
||||
if (calculate_epsilon) {
|
||||
// Calculate epsilon from average triangle edge length.
|
||||
double l = its_average_edge_length(its);
|
||||
if (l > 0)
|
||||
m_triangle_ray_epsilon = 0.000001 * l * l;
|
||||
}
|
||||
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
its.vertices, its.indices);
|
||||
}
|
||||
|
||||
void intersect_ray(const indexed_triangle_set &its,
|
||||
const Vec3d & s,
|
||||
const Vec3d & dir,
|
||||
igl::Hit & hit)
|
||||
{
|
||||
AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices,
|
||||
m_tree, s, dir, hit, m_triangle_ray_epsilon);
|
||||
}
|
||||
|
||||
void intersect_ray(const indexed_triangle_set &its,
|
||||
const Vec3d & s,
|
||||
const Vec3d & dir,
|
||||
std::vector<igl::Hit> & hits)
|
||||
{
|
||||
AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices,
|
||||
m_tree, s, dir, hits, m_triangle_ray_epsilon);
|
||||
}
|
||||
|
||||
double squared_distance(const indexed_triangle_set & its,
|
||||
const Vec3d & point,
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
}
|
||||
};
|
||||
|
||||
template<class M> void AABBMesh::init(const M &mesh, bool calculate_epsilon)
|
||||
{
|
||||
// Build the AABB accelaration tree
|
||||
m_aabb->init(*m_tm, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon)
|
||||
: m_tm(&tmesh)
|
||||
, m_aabb(new AABBImpl())
|
||||
, m_vfidx{tmesh}
|
||||
, m_fnidx{its_face_neighbors(tmesh)}
|
||||
{
|
||||
init(tmesh, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon)
|
||||
: m_tm(&mesh.its)
|
||||
, m_aabb(new AABBImpl())
|
||||
, m_vfidx{mesh.its}
|
||||
, m_fnidx{its_face_neighbors(mesh.its)}
|
||||
{
|
||||
init(mesh, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::~AABBMesh() {}
|
||||
|
||||
AABBMesh::AABBMesh(const AABBMesh &other)
|
||||
: m_tm(other.m_tm)
|
||||
, m_aabb(new AABBImpl(*other.m_aabb))
|
||||
, m_vfidx{other.m_vfidx}
|
||||
, m_fnidx{other.m_fnidx}
|
||||
{}
|
||||
|
||||
AABBMesh &AABBMesh::operator=(const AABBMesh &other)
|
||||
{
|
||||
m_tm = other.m_tm;
|
||||
m_aabb.reset(new AABBImpl(*other.m_aabb));
|
||||
m_vfidx = other.m_vfidx;
|
||||
m_fnidx = other.m_fnidx;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AABBMesh &AABBMesh::operator=(AABBMesh &&other) = default;
|
||||
|
||||
AABBMesh::AABBMesh(AABBMesh &&other) = default;
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3f>& AABBMesh::vertices() const
|
||||
{
|
||||
return m_tm->vertices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3i32>& AABBMesh::indices() const
|
||||
{
|
||||
return m_tm->indices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3f& AABBMesh::vertices(size_t idx) const
|
||||
{
|
||||
return m_tm->vertices[idx];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3i32& AABBMesh::indices(size_t idx) const
|
||||
{
|
||||
return m_tm->indices[idx];
|
||||
}
|
||||
|
||||
|
||||
Vec3d AABBMesh::normal_by_face_id(int face_id) const {
|
||||
|
||||
return its_unnormalized_normal(*m_tm, face_id).cast<double>().normalized();
|
||||
}
|
||||
|
||||
|
||||
AABBMesh::hit_result
|
||||
AABBMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
assert(is_approx(dir.norm(), 1.));
|
||||
igl::Hit hit{-1, -1, 0.f, 0.f, 0.f};
|
||||
hit.t = std::numeric_limits<float>::infinity();
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
if (! m_holes.empty()) {
|
||||
|
||||
// If there are holes, the hit_results will be made by
|
||||
// query_ray_hits (object) and filter_hits (holes):
|
||||
return filter_hits(query_ray_hits(s, dir));
|
||||
}
|
||||
#endif
|
||||
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hit);
|
||||
hit_result ret(*this);
|
||||
ret.m_t = double(hit.t);
|
||||
ret.m_dir = dir;
|
||||
ret.m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
|
||||
ret.m_normal = this->normal_by_face_id(hit.id);
|
||||
ret.m_face_id = hit.id;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<AABBMesh::hit_result>
|
||||
AABBMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<AABBMesh::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hits);
|
||||
|
||||
// The sort is necessary, the hits are not always sorted.
|
||||
std::sort(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
||||
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
for (const igl::Hit& hit : hits) {
|
||||
outs.emplace_back(AABBMesh::hit_result(*this));
|
||||
outs.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
|
||||
outs.back().m_normal = this->normal_by_face_id(hit.id);
|
||||
outs.back().m_face_id = hit.id;
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
AABBMesh::hit_result IndexedMesh::filter_hits(
|
||||
const std::vector<AABBMesh::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
|
||||
if (object_hits.empty())
|
||||
return out;
|
||||
|
||||
const Vec3d& s = object_hits.front().source();
|
||||
const Vec3d& dir = object_hits.front().direction();
|
||||
|
||||
// A helper struct to save an intersetion with a hole
|
||||
struct HoleHit {
|
||||
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
|
||||
t(t_p), normal(normal_p), entry(entry_p) {}
|
||||
float t;
|
||||
Vec3d normal;
|
||||
bool entry;
|
||||
};
|
||||
std::vector<HoleHit> hole_isects;
|
||||
hole_isects.reserve(m_holes.size());
|
||||
|
||||
auto sf = s.cast<float>();
|
||||
auto dirf = dir.cast<float>();
|
||||
|
||||
// Collect hits on all holes, preserve information about entry/exit
|
||||
for (const sla::DrainHole& hole : m_holes) {
|
||||
std::array<std::pair<float, Vec3d>, 2> isects;
|
||||
if (hole.get_intersections(sf, dirf, isects)) {
|
||||
// Ignore hole hits behind the source
|
||||
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
|
||||
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Holes can intersect each other, sort the hits by t
|
||||
std::sort(hole_isects.begin(), hole_isects.end(),
|
||||
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
|
||||
|
||||
// Now inspect the intersections with object and holes, in the order of
|
||||
// increasing distance. Keep track how deep are we nested in mesh/holes and
|
||||
// pick the correct intersection.
|
||||
// This needs to be done twice - first to find out how deep in the structure
|
||||
// the source is, then to pick the correct intersection.
|
||||
int hole_nested = 0;
|
||||
int object_nested = 0;
|
||||
for (int dry_run=1; dry_run>=0; --dry_run) {
|
||||
hole_nested = -hole_nested;
|
||||
object_nested = -object_nested;
|
||||
|
||||
bool is_hole = false;
|
||||
bool is_entry = false;
|
||||
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
|
||||
const hit_result* next_mesh_hit = &object_hits.front();
|
||||
|
||||
while (next_hole_hit || next_mesh_hit) {
|
||||
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
|
||||
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
|
||||
else
|
||||
is_hole = next_hole_hit; // one or the other ran out
|
||||
|
||||
// Is this entry or exit hit?
|
||||
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
|
||||
|
||||
if (! dry_run) {
|
||||
if (! is_hole && hole_nested == 0) {
|
||||
// This is a valid object hit
|
||||
return *next_mesh_hit;
|
||||
}
|
||||
if (is_hole && ! is_entry && object_nested != 0) {
|
||||
// This holehit is the one we seek
|
||||
out.m_t = next_hole_hit->t;
|
||||
out.m_normal = next_hole_hit->normal;
|
||||
out.m_source = s;
|
||||
out.m_dir = dir;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase/decrease the counter
|
||||
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
|
||||
|
||||
// Advance the respective pointer
|
||||
if (is_hole && next_hole_hit++ == &hole_isects.back())
|
||||
next_hole_hit = nullptr;
|
||||
if (! is_hole && next_mesh_hit++ == &object_hits.back())
|
||||
next_mesh_hit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, the ray ended up in infinity
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,142 @@
|
||||
#ifndef PRUSASLICER_AABBMESH_H
|
||||
#define PRUSASLICER_AABBMESH_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// There is an implementation of a hole-aware raycaster that was eventually
|
||||
// not used in production version. It is now hidden under following define
|
||||
// for possible future use.
|
||||
// #define SLIC3R_HOLE_RAYCASTER
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#endif
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
// An index-triangle structure coupled with an AABB index to support ray
|
||||
// casting and other higher level operations.
|
||||
class AABBMesh {
|
||||
class AABBImpl;
|
||||
|
||||
const indexed_triangle_set* m_tm;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
VertexFaceIndex m_vfidx; // vertex-face index
|
||||
std::vector<Vec3i32> m_fnidx; // face-neighbor index
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
// This holds a copy of holes in the mesh. Initialized externally
|
||||
// by load_mesh setter.
|
||||
std::vector<sla::DrainHole> m_holes;
|
||||
#endif
|
||||
|
||||
template<class M> void init(const M &mesh, bool calculate_epsilon);
|
||||
|
||||
public:
|
||||
|
||||
// calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length.
|
||||
// If set to false, a default epsilon is used, which works for "reasonable" meshes.
|
||||
explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false);
|
||||
explicit AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon = false);
|
||||
|
||||
AABBMesh(const AABBMesh& other);
|
||||
AABBMesh& operator=(const AABBMesh&);
|
||||
|
||||
AABBMesh(AABBMesh &&other);
|
||||
AABBMesh& operator=(AABBMesh &&other);
|
||||
|
||||
~AABBMesh();
|
||||
|
||||
const std::vector<Vec3f>& vertices() const;
|
||||
const std::vector<Vec3i32>& indices() const;
|
||||
const Vec3f& vertices(size_t idx) const;
|
||||
const Vec3i32& indices(size_t idx) const;
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
// m_t holds a distance from m_source to the intersection.
|
||||
double m_t = infty();
|
||||
int m_face_id = -1;
|
||||
const AABBMesh *m_mesh = nullptr;
|
||||
Vec3d m_dir = Vec3d::Zero();
|
||||
Vec3d m_source = Vec3d::Zero();
|
||||
Vec3d m_normal = Vec3d::Zero();
|
||||
friend class AABBMesh;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
// IndexedMesh::query_ray_hit method.
|
||||
explicit inline hit_result(const AABBMesh& em): m_mesh(&em) {}
|
||||
public:
|
||||
// This denotes no hit on the mesh.
|
||||
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||
|
||||
explicit inline hit_result(double val = infty()) : m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
inline const Vec3d& source() const { return m_source; }
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline int face() const { return m_face_id; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
|
||||
|
||||
inline const Vec3d& normal() const {
|
||||
assert(is_valid());
|
||||
return m_normal;
|
||||
}
|
||||
|
||||
inline bool is_inside() const {
|
||||
return is_hit() && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
// Inform the object about location of holes
|
||||
// creates internal copy of the vector
|
||||
void load_holes(const std::vector<sla::DrainHole>& holes) {
|
||||
m_holes = holes;
|
||||
}
|
||||
|
||||
// Iterates over hits and holes and returns the true hit, possibly
|
||||
// on the inside of a hole.
|
||||
// This function is currently not used anywhere, it was written when the
|
||||
// holes were subtracted on slices, that is, before we started using CGAL
|
||||
// to actually cut the holes into the mesh.
|
||||
hit_result filter_hits(const std::vector<AABBMesh::hit_result>& obj_hits) const;
|
||||
#endif
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Casts a ray on the mesh and returns all hits
|
||||
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
|
||||
Vec3d normal_by_face_id(int face_id) const;
|
||||
|
||||
const indexed_triangle_set * get_triangle_mesh() const { return m_tm; }
|
||||
|
||||
const VertexFaceIndex &vertex_face_index() const { return m_vfidx; }
|
||||
const std::vector<Vec3i32> &face_neighbor_index() const { return m_fnidx; }
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
||||
#endif // INDEXEDMESH_H
|
||||
@@ -0,0 +1,992 @@
|
||||
// AABB tree built upon external data set, referencing the external data by integer indices.
|
||||
// The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh)
|
||||
// were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson <alecjacobson@gmail.com>
|
||||
// while the implicit balanced tree representation and memory optimizations are Vojtech's.
|
||||
|
||||
#ifndef slic3r_AABBTreeIndirect_hpp_
|
||||
#define slic3r_AABBTreeIndirect_hpp_
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||
|
||||
// Definition of the ray intersection hit structure.
|
||||
#include <igl/Hit.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace AABBTreeIndirect {
|
||||
|
||||
// Static balanced AABB tree for raycasting and closest triangle search.
|
||||
// The balanced tree is built over a single large std::vector of nodes, where the children of nodes
|
||||
// are addressed implicitely using a power of two indexing rule.
|
||||
// Memory for a full balanced tree is allocated, but not all nodes at the last level are used.
|
||||
// This may seem like a waste of memory, but one saves memory for the node links and there is zero
|
||||
// overhead of a memory allocator management (usually the memory allocator adds at least one pointer
|
||||
// before the memory returned). However, allocating memory in a single vector is very fast even
|
||||
// in multi-threaded environment and it is cache friendly.
|
||||
//
|
||||
// A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference
|
||||
// to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc).
|
||||
// The source bounding boxes may have an epsilon applied to fight numeric rounding errors when
|
||||
// traversing the AABB tree.
|
||||
template<int ANumDimensions, typename ACoordType>
|
||||
class Tree
|
||||
{
|
||||
public:
|
||||
static constexpr int NumDimensions = ANumDimensions;
|
||||
using CoordType = ACoordType;
|
||||
using VectorType = Eigen::Matrix<CoordType, NumDimensions, 1, Eigen::DontAlign>;
|
||||
using BoundingBox = Eigen::AlignedBox<CoordType, NumDimensions>;
|
||||
// Following could be static constexpr size_t, but that would not link in C++11
|
||||
enum : size_t {
|
||||
// Node is not used.
|
||||
npos = size_t(-1),
|
||||
// Inner node (not leaf).
|
||||
inner = size_t(-2)
|
||||
};
|
||||
|
||||
// Single node of the implicit balanced AABB tree. There are no links to the children nodes,
|
||||
// as these links are calculated implicitely using a power of two rule.
|
||||
struct Node {
|
||||
// Index of the external source entity, for which this AABB tree was built, npos for internal nodes.
|
||||
size_t idx = npos;
|
||||
// Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors
|
||||
// when traversing the AABB tree.
|
||||
BoundingBox bbox;
|
||||
|
||||
bool is_valid() const { return this->idx != npos; }
|
||||
bool is_inner() const { return this->idx == inner; }
|
||||
bool is_leaf() const { return ! this->is_inner(); }
|
||||
|
||||
template<typename SourceNode>
|
||||
void set(const SourceNode &rhs) {
|
||||
this->idx = rhs.idx();
|
||||
this->bbox = rhs.bbox();
|
||||
}
|
||||
};
|
||||
|
||||
void clear() { m_nodes.clear(); }
|
||||
|
||||
// SourceNode shall implement
|
||||
// size_t SourceNode::idx() const
|
||||
// - Index to the outside entity (triangle, edge, point etc).
|
||||
// const VectorType& SourceNode::centroid() const
|
||||
// - Centroid of this node. The centroid is used for balancing the tree.
|
||||
// const BoundingBox& SourceNode::bbox() const
|
||||
// - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal.
|
||||
// Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension
|
||||
// to split around.
|
||||
template<typename SourceNode>
|
||||
void build(std::vector<SourceNode> &&input)
|
||||
{
|
||||
this->build_modify_input(input);
|
||||
input.clear();
|
||||
}
|
||||
|
||||
template<typename SourceNode>
|
||||
void build_modify_input(std::vector<SourceNode> &input)
|
||||
{
|
||||
if (input.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node());
|
||||
build_recursive(input, 0, 0, input.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Node>& nodes() const { return m_nodes; }
|
||||
const Node& node(size_t idx) const { return m_nodes[idx]; }
|
||||
bool empty() const { return m_nodes.empty(); }
|
||||
|
||||
// Addressing the child nodes using the power of two rule.
|
||||
static size_t left_child_idx(size_t idx) { return idx * 2 + 1; }
|
||||
static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; }
|
||||
const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; }
|
||||
const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; }
|
||||
|
||||
template<typename SourceNode>
|
||||
void build(const std::vector<SourceNode> &input)
|
||||
{
|
||||
std::vector<SourceNode> copy(input);
|
||||
this->build(std::move(copy));
|
||||
}
|
||||
|
||||
private:
|
||||
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||
template<typename SourceNode>
|
||||
void build_recursive(std::vector<SourceNode> &input, size_t node, const size_t left, const size_t right)
|
||||
{
|
||||
assert(node < m_nodes.size());
|
||||
assert(left <= right);
|
||||
|
||||
if (left == right) {
|
||||
// Insert a node into the balanced tree.
|
||||
m_nodes[node].set(input[left]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate bounding box of the input.
|
||||
BoundingBox bbox(input[left].bbox());
|
||||
for (size_t i = left + 1; i <= right; ++ i)
|
||||
bbox.extend(input[i].bbox());
|
||||
int dimension = -1;
|
||||
bbox.diagonal().maxCoeff(&dimension);
|
||||
|
||||
// Partition the input to left / right pieces of the same length to produce a balanced tree.
|
||||
size_t center = (left + right) / 2;
|
||||
partition_input(input, size_t(dimension), left, right, center);
|
||||
// Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc).
|
||||
m_nodes[node].idx = inner;
|
||||
m_nodes[node].bbox = bbox;
|
||||
build_recursive(input, node * 2 + 1, left, center);
|
||||
build_recursive(input, node * 2 + 2, center + 1, right);
|
||||
}
|
||||
|
||||
// Partition the input m_nodes <left, right> at "k" and "dimension" using the QuickSelect method:
|
||||
// https://en.wikipedia.org/wiki/Quickselect
|
||||
// Items left of the k'th item are lower than the k'th item in the "dimension",
|
||||
// items right of the k'th item are higher than the k'th item in the "dimension",
|
||||
template<typename SourceNode>
|
||||
void partition_input(std::vector<SourceNode> &input, const size_t dimension, size_t left, size_t right, const size_t k) const
|
||||
{
|
||||
while (left < right) {
|
||||
size_t center = (left + right) / 2;
|
||||
CoordType pivot;
|
||||
{
|
||||
// Bubble sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = input[left ].centroid()(dimension);
|
||||
CoordType center_value = input[center].centroid()(dimension);
|
||||
CoordType right_value = input[right ].centroid()(dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right <= left + 2)
|
||||
// The <left, right> interval is already sorted.
|
||||
break;
|
||||
size_t i = left;
|
||||
size_t j = right - 1;
|
||||
std::swap(input[center], input[j]);
|
||||
// Partition the set based on the pivot.
|
||||
for (;;) {
|
||||
// Skip left points that are already at correct positions.
|
||||
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||
while (input[++ i].centroid()(dimension) < pivot) ;
|
||||
// Skip right points that are already at correct positions.
|
||||
while (input[-- j].centroid()(dimension) > pivot && i < j) ;
|
||||
if (i >= j)
|
||||
break;
|
||||
std::swap(input[i], input[j]);
|
||||
}
|
||||
// Restore pivot to the center of the sequence.
|
||||
std::swap(input[i], input[right - 1]);
|
||||
// Which side the kth element is in?
|
||||
if (k < i)
|
||||
right = i - 1;
|
||||
else if (k == i)
|
||||
// Sequence is partitioned, kth element is at its place.
|
||||
break;
|
||||
else
|
||||
left = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The balanced tree storage.
|
||||
std::vector<Node> m_nodes;
|
||||
};
|
||||
|
||||
using Tree2f = Tree<2, float>;
|
||||
using Tree3f = Tree<3, float>;
|
||||
using Tree2d = Tree<2, double>;
|
||||
using Tree3d = Tree<3, double>;
|
||||
|
||||
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
|
||||
// to build an AABBTree over coord_t 2D bounding boxes.
|
||||
class BoundingBoxWrapper {
|
||||
public:
|
||||
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
|
||||
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return (m_bbox.min() + m_bbox.max() / 2); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
const VectorType dir;
|
||||
const VectorType invdir;
|
||||
|
||||
// epsilon for ray-triangle intersection, see intersect_triangle1()
|
||||
const double eps;
|
||||
};
|
||||
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
struct RayIntersectorHits : RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
std::vector<igl::Hit> hits;
|
||||
};
|
||||
|
||||
//FIXME implement SSE for float AABB trees with float ray queries.
|
||||
// SSE/SSE2 is supported by any Intel/AMD x64 processor.
|
||||
// SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats,
|
||||
// storing the node index as the 4th element of the bounding box min value etc.
|
||||
// https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml
|
||||
template <typename Derivedsource, typename Deriveddir, typename Scalar>
|
||||
inline bool ray_box_intersect_invdir(
|
||||
const Eigen::MatrixBase<Derivedsource> &origin,
|
||||
const Eigen::MatrixBase<Deriveddir> &inv_dir,
|
||||
Eigen::AlignedBox<Scalar,3> box,
|
||||
const Scalar &t0,
|
||||
const Scalar &t1) {
|
||||
// http://people.csail.mit.edu/amy/papers/box-jgt.pdf
|
||||
// "An Efficient and Robust Ray–Box Intersection Algorithm"
|
||||
if (inv_dir.x() < 0)
|
||||
std::swap(box.min().x(), box.max().x());
|
||||
if (inv_dir.y() < 0)
|
||||
std::swap(box.min().y(), box.max().y());
|
||||
Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y();
|
||||
if (tmin > tymax)
|
||||
return false;
|
||||
Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y();
|
||||
if (tymin > tmax)
|
||||
return false;
|
||||
if (tymin > tmin)
|
||||
tmin = tymin;
|
||||
if (tymax < tmax)
|
||||
tmax = tymax;
|
||||
if (inv_dir.z() < 0)
|
||||
std::swap(box.min().z(), box.max().z());
|
||||
Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z();
|
||||
if (tzmin > tmax)
|
||||
return false;
|
||||
Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z();
|
||||
if (tmin > tzmax)
|
||||
return false;
|
||||
if (tzmin > tmin)
|
||||
tmin = tzmin;
|
||||
if (tzmax < tmax)
|
||||
tmax = tzmax;
|
||||
return tmin < t1 && tmax > t0;
|
||||
}
|
||||
|
||||
// The following intersect_triangle() is derived from raytri.c routine intersect_triangle1()
|
||||
// Ray-Triangle Intersection Test Routines
|
||||
// Different optimizations of my and Ben Trumbore's
|
||||
// code from journals of graphics tools (JGT)
|
||||
// http://www.acm.org/jgt/
|
||||
// by Tomas Moller, May 2000
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value&& std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &orig, const V &dir, const W &vert0, const W &vert1, const W &vert2, double &t, double &u, double &v, double eps)
|
||||
{
|
||||
// find vectors for two edges sharing vert0
|
||||
const V edge1 = vert1 - vert0;
|
||||
const V edge2 = vert2 - vert0;
|
||||
// begin calculating determinant - also used to calculate U parameter
|
||||
const V pvec = dir.cross(edge2);
|
||||
// if determinant is near zero, ray lies in plane of triangle
|
||||
const double det = edge1.dot(pvec);
|
||||
V qvec;
|
||||
|
||||
if (det > eps) {
|
||||
// calculate distance from vert0 to ray origin
|
||||
V tvec = orig - vert0;
|
||||
// calculate U parameter and test bounds
|
||||
u = tvec.dot(pvec);
|
||||
if (u < 0.0 || u > det)
|
||||
return false;
|
||||
// prepare to test V parameter
|
||||
qvec = tvec.cross(edge1);
|
||||
// calculate V parameter and test bounds
|
||||
v = dir.dot(qvec);
|
||||
if (v < 0.0 || u + v > det)
|
||||
return false;
|
||||
} else if (det < -eps) {
|
||||
// calculate distance from vert0 to ray origin
|
||||
V tvec = orig - vert0;
|
||||
// calculate U parameter and test bounds
|
||||
u = tvec.dot(pvec);
|
||||
if (u > 0.0 || u < det)
|
||||
return false;
|
||||
// prepare to test V parameter
|
||||
qvec = tvec.cross(edge1);
|
||||
// calculate V parameter and test bounds
|
||||
v = dir.dot(qvec);
|
||||
if (v > 0.0 || u + v < det)
|
||||
return false;
|
||||
} else
|
||||
// ray is parallel to the plane of the triangle
|
||||
return false;
|
||||
|
||||
double inv_det = 1.0 / det;
|
||||
// calculate t, ray intersects triangle
|
||||
t = edge2.dot(qvec) * inv_det;
|
||||
u *= inv_det;
|
||||
v *= inv_det;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin, dir, v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0, v1, v2, t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename Tree>
|
||||
double intersect_triangle_epsilon(const Tree &tree) {
|
||||
double eps = 0.000001;
|
||||
if (! tree.empty()) {
|
||||
const typename Tree::BoundingBox &bbox = tree.nodes().front().bbox;
|
||||
double l = (bbox.max() - bbox.min()).cwiseMax();
|
||||
if (l > 0)
|
||||
eps /= (l * l);
|
||||
}
|
||||
return eps;
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType, typename Scalar>
|
||||
static inline bool intersect_ray_recursive_first_hit(
|
||||
RayIntersectorType &ray_intersector,
|
||||
size_t node_idx,
|
||||
Scalar min_t,
|
||||
igl::Hit &hit)
|
||||
{
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(), Scalar(0), min_t))
|
||||
return false;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
// shoot ray, record hit
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v, ray_intersector.eps)
|
||||
&& t > 0.) {
|
||||
hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) };
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
igl::Hit left_hit;
|
||||
igl::Hit right_hit;
|
||||
bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit);
|
||||
if (left_ret && left_hit.t < min_t) {
|
||||
min_t = left_hit.t;
|
||||
hit = left_hit;
|
||||
} else
|
||||
left_ret = false;
|
||||
bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit);
|
||||
if (right_ret && right_hit.t < min_t)
|
||||
hit = right_hit;
|
||||
else
|
||||
right_ret = false;
|
||||
return left_ret || right_ret;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType>
|
||||
static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx)
|
||||
{
|
||||
using Scalar = typename RayIntersectorType::VectorType::Scalar;
|
||||
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(),
|
||||
Scalar(0), std::numeric_limits<Scalar>::infinity()))
|
||||
return;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v, ray_intersector.eps)
|
||||
&& t > 0.) {
|
||||
ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) });
|
||||
}
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
intersect_ray_recursive_all_hits(ray_intersector, left);
|
||||
intersect_ray_recursive_all_hits(ray_intersector, right);
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time collision detection, Ericson, Chapter 5
|
||||
template<typename Vector>
|
||||
static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
// Check if P in vertex region outside A
|
||||
Vector ab = b - a;
|
||||
Vector ac = c - a;
|
||||
Vector ap = p - a;
|
||||
Scalar d1 = ab.dot(ap);
|
||||
Scalar d2 = ac.dot(ap);
|
||||
if (d1 <= 0 && d2 <= 0)
|
||||
return a;
|
||||
// Check if P in vertex region outside B
|
||||
Vector bp = p - b;
|
||||
Scalar d3 = ab.dot(bp);
|
||||
Scalar d4 = ac.dot(bp);
|
||||
if (d3 >= 0 && d4 <= d3)
|
||||
return b;
|
||||
// Check if P in edge region of AB, if so return projection of P onto AB
|
||||
Scalar vc = d1*d4 - d3*d2;
|
||||
if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) {
|
||||
Scalar v = d1 / (d1 - d3);
|
||||
return a + v * ab;
|
||||
}
|
||||
// Check if P in vertex region outside C
|
||||
Vector cp = p - c;
|
||||
Scalar d5 = ab.dot(cp);
|
||||
Scalar d6 = ac.dot(cp);
|
||||
if (d6 >= 0 && d5 <= d6)
|
||||
return c;
|
||||
// Check if P in edge region of AC, if so return projection of P onto AC
|
||||
Scalar vb = d5*d2 - d1*d6;
|
||||
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
||||
Scalar w = d2 / (d2 - d6);
|
||||
return a + w * ac;
|
||||
}
|
||||
// Check if P in edge region of BC, if so return projection of P onto BC
|
||||
Scalar va = d3*d6 - d5*d4;
|
||||
if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {
|
||||
Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
||||
return b + w * (c - b);
|
||||
}
|
||||
// P inside face region. Compute Q through its barycentric coordinates (u,v,w)
|
||||
Scalar denom = Scalar(1.0) / (va + vb + vc);
|
||||
Scalar v = vb * denom;
|
||||
Scalar w = vc * denom;
|
||||
return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w
|
||||
};
|
||||
|
||||
|
||||
// Nothing to do with COVID-19 social distancing.
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct IndexedTriangleSetDistancer {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
using ScalarType = typename VectorType::Scalar;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index,
|
||||
ScalarType& squared_distance) const {
|
||||
const auto &triangle = this->faces[primitive_index];
|
||||
VectorType closest_point = closest_point_to_triangle<VectorType>(origin,
|
||||
this->vertices[triangle(0)].template cast<ScalarType>(),
|
||||
this->vertices[triangle(1)].template cast<ScalarType>(),
|
||||
this->vertices[triangle(2)].template cast<ScalarType>());
|
||||
squared_distance = (origin - closest_point).squaredNorm();
|
||||
return closest_point;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename IndexedPrimitivesDistancerType, typename Scalar>
|
||||
static inline Scalar squared_distance_to_indexed_primitives_recursive(
|
||||
IndexedPrimitivesDistancerType &distancer,
|
||||
size_t node_idx,
|
||||
Scalar low_sqr_d,
|
||||
Scalar up_sqr_d,
|
||||
size_t &i,
|
||||
Eigen::PlainObjectBase<typename IndexedPrimitivesDistancerType::VectorType> &c)
|
||||
{
|
||||
using Vector = typename IndexedPrimitivesDistancerType::VectorType;
|
||||
|
||||
if (low_sqr_d > up_sqr_d)
|
||||
return low_sqr_d;
|
||||
|
||||
// Save the best achieved hit.
|
||||
auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) {
|
||||
if (sqr_d_candidate < up_sqr_d) {
|
||||
i = i_candidate;
|
||||
c = c_candidate;
|
||||
up_sqr_d = sqr_d_candidate;
|
||||
}
|
||||
};
|
||||
|
||||
const auto &node = distancer.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf())
|
||||
{
|
||||
Scalar sqr_dist;
|
||||
Vector c_candidate = distancer.closest_point_to_origin(node.idx, sqr_dist);
|
||||
set_min(sqr_dist, node.idx, c_candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = distancer.tree.node(left_node_idx);
|
||||
const auto &node_right = distancer.tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
bool looked_left = false;
|
||||
bool looked_right = false;
|
||||
const auto &look_left = [&]()
|
||||
{
|
||||
size_t i_left;
|
||||
Vector c_left = c;
|
||||
Scalar sqr_d_left = squared_distance_to_indexed_primitives_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left);
|
||||
set_min(sqr_d_left, i_left, c_left);
|
||||
looked_left = true;
|
||||
};
|
||||
const auto &look_right = [&]()
|
||||
{
|
||||
size_t i_right;
|
||||
Vector c_right = c;
|
||||
Scalar sqr_d_right = squared_distance_to_indexed_primitives_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right);
|
||||
set_min(sqr_d_right, i_right, c_right);
|
||||
looked_right = true;
|
||||
};
|
||||
|
||||
// must look left or right if in box
|
||||
using BBoxScalar = typename IndexedPrimitivesDistancerType::TreeType::BoundingBox::Scalar;
|
||||
if (node_left.bbox.contains(distancer.origin.template cast<BBoxScalar>()))
|
||||
look_left();
|
||||
if (node_right.bbox.contains(distancer.origin.template cast<BBoxScalar>()))
|
||||
look_right();
|
||||
// if haven't looked left and could be less than current min, then look
|
||||
Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin);
|
||||
Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin);
|
||||
if (left_up_sqr_d < right_up_sqr_d) {
|
||||
if (! looked_left && left_up_sqr_d < up_sqr_d)
|
||||
look_left();
|
||||
if (! looked_right && right_up_sqr_d < up_sqr_d)
|
||||
look_right();
|
||||
} else {
|
||||
if (! looked_right && right_up_sqr_d < up_sqr_d)
|
||||
look_right();
|
||||
if (! looked_left && left_up_sqr_d < up_sqr_d)
|
||||
look_left();
|
||||
}
|
||||
}
|
||||
return up_sqr_d;
|
||||
}
|
||||
|
||||
template<typename IndexedPrimitivesDistancerType, typename Scalar>
|
||||
static inline void indexed_primitives_within_distance_squared_recurisve(const IndexedPrimitivesDistancerType &distancer,
|
||||
size_t node_idx,
|
||||
Scalar squared_distance_limit,
|
||||
std::vector<size_t> &found_primitives_indices)
|
||||
{
|
||||
const auto &node = distancer.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf()) {
|
||||
Scalar sqr_dist;
|
||||
distancer.closest_point_to_origin(node.idx, sqr_dist);
|
||||
if (sqr_dist < squared_distance_limit) { found_primitives_indices.push_back(node.idx); }
|
||||
} else {
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = distancer.tree.node(left_node_idx);
|
||||
const auto &node_right = distancer.tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
if (node_left.bbox.squaredExteriorDistance(distancer.origin) < squared_distance_limit) {
|
||||
indexed_primitives_within_distance_squared_recurisve(distancer, left_node_idx, squared_distance_limit,
|
||||
found_primitives_indices);
|
||||
}
|
||||
if (node_right.bbox.squaredExteriorDistance(distancer.origin) < squared_distance_limit) {
|
||||
indexed_primitives_within_distance_squared_recurisve(distancer, right_node_idx, squared_distance_limit,
|
||||
found_primitives_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Build a balanced AABB Tree over an indexed triangles set, balancing the tree
|
||||
// on centroids of the triangles.
|
||||
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
|
||||
// during tree traversal.
|
||||
template<typename VertexType, typename IndexedFaceType>
|
||||
inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
//FIXME do we want to apply an epsilon?
|
||||
const typename VertexType::Scalar eps = 0)
|
||||
{
|
||||
using TreeType = Tree<3, typename VertexType::Scalar>;
|
||||
// using CoordType = typename TreeType::CoordType;
|
||||
using VectorType = typename TreeType::VectorType;
|
||||
using BoundingBox = typename TreeType::BoundingBox;
|
||||
|
||||
struct InputType {
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
const VectorType& centroid() const { return m_centroid; }
|
||||
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
VectorType m_centroid;
|
||||
};
|
||||
|
||||
std::vector<InputType> input;
|
||||
input.reserve(faces.size());
|
||||
const VectorType veps(eps, eps, eps);
|
||||
for (size_t i = 0; i < faces.size(); ++ i) {
|
||||
const IndexedFaceType &face = faces[i];
|
||||
const VertexType &v1 = vertices[face(0)];
|
||||
const VertexType &v2 = vertices[face(1)];
|
||||
const VertexType &v3 = vertices[face(2)];
|
||||
InputType n;
|
||||
n.m_idx = i;
|
||||
n.m_centroid = (1./3.) * (v1 + v2 + v3);
|
||||
n.m_bbox = BoundingBox(v1, v1);
|
||||
n.m_bbox.extend(v2);
|
||||
n.m_bbox.extend(v3);
|
||||
n.m_bbox.min() -= veps;
|
||||
n.m_bbox.max() += veps;
|
||||
input.emplace_back(n);
|
||||
}
|
||||
|
||||
TreeType out;
|
||||
out.build(std::move(input));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Find a first intersection of a ray with indexed triangle set.
|
||||
// Intersection test is calculated with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool intersect_ray_first_hit(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Origin of the ray.
|
||||
const VectorType &origin,
|
||||
// Direction of the ray.
|
||||
const VectorType &dir,
|
||||
// First intersection of the ray with the indexed triangle set.
|
||||
igl::Hit &hit,
|
||||
// Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length.
|
||||
const double eps = 0.000001)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto ray_intersector = detail::RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
vertices, faces, tree,
|
||||
origin, dir, VectorType(dir.cwiseInverse()),
|
||||
eps
|
||||
};
|
||||
return ! tree.empty() && detail::intersect_ray_recursive_first_hit(
|
||||
ray_intersector, size_t(0), std::numeric_limits<Scalar>::infinity(), hit);
|
||||
}
|
||||
|
||||
// Find all intersections of a ray with indexed triangle set.
|
||||
// Intersection test is calculated with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// The output hits are sorted by the ray parameter.
|
||||
// If the ray intersects a shared edge of two triangles, hits for both triangles are returned.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool intersect_ray_all_hits(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Origin of the ray.
|
||||
const VectorType &origin,
|
||||
// Direction of the ray.
|
||||
const VectorType &dir,
|
||||
// All intersections of the ray with the indexed triangle set, sorted by parameter t.
|
||||
std::vector<igl::Hit> &hits,
|
||||
// Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length.
|
||||
const double eps = 0.000001)
|
||||
{
|
||||
auto ray_intersector = detail::RayIntersectorHits<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
{ vertices, faces, {tree},
|
||||
origin, dir, VectorType(dir.cwiseInverse()),
|
||||
eps }
|
||||
};
|
||||
if (tree.empty()) {
|
||||
hits.clear();
|
||||
} else {
|
||||
// Reusing the output memory if there is some memory already pre-allocated.
|
||||
ray_intersector.hits = std::move(hits);
|
||||
ray_intersector.hits.clear();
|
||||
ray_intersector.hits.reserve(8);
|
||||
detail::intersect_ray_recursive_all_hits(ray_intersector, 0);
|
||||
hits = std::move(ray_intersector.hits);
|
||||
std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; });
|
||||
}
|
||||
return ! hits.empty();
|
||||
}
|
||||
|
||||
// Finding a closest triangle, its closest point and squared distance to the closest point
|
||||
// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns squared distance to the closest point or -1 if the input is empty.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the closest point on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
// Index of the closest triangle in faces.
|
||||
size_t &hit_idx_out,
|
||||
// Position of the closest point on the indexed triangle set.
|
||||
Eigen::PlainObjectBase<VectorType> &hit_point_out)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
return tree.empty() ? Scalar(-1) :
|
||||
detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
|
||||
}
|
||||
|
||||
// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns true if exists some triangle in defined radius, false otherwise.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool is_any_triangle_in_radius(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the closest point on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
//Square of maximum distance in which triangle is searched for
|
||||
typename VectorType::Scalar &max_distance_squared)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
|
||||
size_t hit_idx;
|
||||
VectorType hit_point = VectorType::Ones() * (NaN<typename VectorType::Scalar>);
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_distance_squared, hit_idx, hit_point);
|
||||
|
||||
return hit_point.allFinite();
|
||||
}
|
||||
|
||||
// Returns all triangles within the given radius limit
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline std::vector<size_t> all_triangles_in_radius(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the distances on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
//Square of maximum distance in which triangles are searched for
|
||||
typename VectorType::Scalar max_distance_squared)
|
||||
{
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<size_t> found_triangles{};
|
||||
detail::indexed_primitives_within_distance_squared_recurisve(distancer, size_t(0), max_distance_squared, found_triangles);
|
||||
return found_triangles;
|
||||
}
|
||||
|
||||
|
||||
// Traverse the tree and return the index of an entity whose bounding box
|
||||
// contains a given point. Returns size_t(-1) when the point is outside.
|
||||
template<typename TreeType, typename VectorType>
|
||||
void get_candidate_idxs(const TreeType& tree, const VectorType& v, std::vector<size_t>& candidates, size_t node_idx = 0)
|
||||
{
|
||||
if (tree.empty() || ! tree.node(node_idx).bbox.contains(v))
|
||||
return;
|
||||
|
||||
decltype(tree.node(node_idx)) node = tree.node(node_idx);
|
||||
static_assert(std::is_reference<decltype(node)>::value,
|
||||
"Nodes shall be addressed by reference.");
|
||||
assert(node.is_valid());
|
||||
assert(node.bbox.contains(v));
|
||||
|
||||
if (! node.is_leaf()) {
|
||||
if (tree.left_child(node_idx).bbox.contains(v))
|
||||
get_candidate_idxs(tree, v, candidates, tree.left_child_idx(node_idx));
|
||||
if (tree.right_child(node_idx).bbox.contains(v))
|
||||
get_candidate_idxs(tree, v, candidates, tree.right_child_idx(node_idx));
|
||||
} else
|
||||
candidates.push_back(node.idx);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Predicate: need to be specialized for intersections of different geomteries
|
||||
template<class G> struct Intersecting {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Intersecting<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Intersecting(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return box.intersects(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto intersecting(const G &g) { return Intersecting<G>{g}; }
|
||||
|
||||
template<class G> struct Within {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Within<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Within(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return node.is_leaf() ? box.contains(node.bbox) : box.intersects(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto within(const G &g) { return Within<G>{g}; }
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Returns true in case traversal should continue,
|
||||
// returns false if traversal should stop (for example if the first hit was found).
|
||||
template<int Dims, typename T, typename Pred, typename Fn>
|
||||
bool traverse_recurse(const Tree<Dims, T> &tree,
|
||||
size_t idx,
|
||||
Pred && pred,
|
||||
Fn && callback)
|
||||
{
|
||||
assert(tree.node(idx).is_valid());
|
||||
|
||||
if (!pred(tree.node(idx)))
|
||||
// Continue traversal.
|
||||
return true;
|
||||
|
||||
if (tree.node(idx).is_leaf()) {
|
||||
// Callback returns true to continue traversal, false to stop traversal.
|
||||
return callback(tree.node(idx));
|
||||
} else {
|
||||
|
||||
// call this with left and right node idx:
|
||||
auto trv = [&](size_t idx) -> bool {
|
||||
return traverse_recurse(tree, idx, std::forward<Pred>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
};
|
||||
|
||||
// Left / right child node index.
|
||||
// Returns true if both children allow the traversal to continue.
|
||||
return trv(Tree<Dims, T>::left_child_idx(idx)) &&
|
||||
trv(Tree<Dims, T>::right_child_idx(idx));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Tree traversal with a predicate. Example usage:
|
||||
// traverse(tree, intersecting(QueryBox), [](size_t face_idx) {
|
||||
// /* ... */
|
||||
// });
|
||||
// Callback shall return true to continue traversal, false if it wants to stop traversal, for example if it found the answer.
|
||||
template<int Dims, typename T, typename Predicate, typename Fn>
|
||||
void traverse(const Tree<Dims, T> &tree, Predicate &&pred, Fn &&callback)
|
||||
{
|
||||
if (tree.empty()) return;
|
||||
|
||||
detail::traverse_recurse(tree, size_t(0), std::forward<Predicate>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
}
|
||||
|
||||
} // namespace AABBTreeIndirect
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AABBTreeIndirect_hpp_ */
|
||||
@@ -0,0 +1,373 @@
|
||||
#ifndef SRC_LIBSLIC3R_AABBTREELINES_HPP_
|
||||
#define SRC_LIBSLIC3R_AABBTREELINES_HPP_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace AABBTreeLines {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename ALineType, typename ATreeType, typename AVectorType>
|
||||
struct IndexedLinesDistancer {
|
||||
using LineType = ALineType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
using ScalarType = typename VectorType::Scalar;
|
||||
|
||||
const std::vector<LineType>& lines;
|
||||
const TreeType& tree;
|
||||
|
||||
const VectorType origin;
|
||||
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType& squared_distance) const
|
||||
{
|
||||
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
|
||||
const LineType& line = lines[primitive_index];
|
||||
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
|
||||
return nearest_point.template cast<ScalarType>();
|
||||
}
|
||||
};
|
||||
|
||||
// returns number of intersections of ray starting in ray_origin and following the specified coordinate line with lines in tree
|
||||
// first number is hits in positive direction of ray, second number hits in negative direction. returns neagtive numbers when ray_origin is
|
||||
// on some line exactly.
|
||||
template <typename LineType, typename TreeType, typename VectorType, int coordinate>
|
||||
inline std::tuple<int, int> coordinate_aligned_ray_hit_count(size_t node_idx,
|
||||
const TreeType& tree,
|
||||
const std::vector<LineType>& lines,
|
||||
const VectorType& ray_origin)
|
||||
{
|
||||
static constexpr int other_coordinate = (coordinate + 1) % 2;
|
||||
using Scalar = typename LineType::Scalar;
|
||||
using Floating = typename std::conditional<std::is_floating_point<Scalar>::value, Scalar, double>::type;
|
||||
const auto& node = tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf()) {
|
||||
const LineType& line = lines[node.idx];
|
||||
if (ray_origin[other_coordinate] < std::min(line.a[other_coordinate], line.b[other_coordinate]) || ray_origin[other_coordinate] >= std::max(line.a[other_coordinate], line.b[other_coordinate])) {
|
||||
// the second inequality is nonsharp for a reason
|
||||
// without it, we may count contour border twice when the lines meet exactly at the spot of intersection. this prevents is
|
||||
return { 0, 0 };
|
||||
}
|
||||
|
||||
Scalar line_max = std::max(line.a[coordinate], line.b[coordinate]);
|
||||
Scalar line_min = std::min(line.a[coordinate], line.b[coordinate]);
|
||||
if (ray_origin[coordinate] > line_max) {
|
||||
return { 1, 0 };
|
||||
} else if (ray_origin[coordinate] < line_min) {
|
||||
return { 0, 1 };
|
||||
} else {
|
||||
// find intersection of ray with line
|
||||
// that is when ( line.a + t * (line.b - line.a) )[other_coordinate] == ray_origin[other_coordinate]
|
||||
// t = ray_origin[oc] - line.a[oc] / (line.b[oc] - line.a[oc]);
|
||||
// then we want to get value of intersection[ coordinate]
|
||||
// val_c = line.a[c] + t * (line.b[c] - line.a[c]);
|
||||
// Note that ray and line may overlap, when (line.b[oc] - line.a[oc]) is zero
|
||||
// In that case, we return negative number
|
||||
Floating distance_oc = line.b[other_coordinate] - line.a[other_coordinate];
|
||||
Floating t = (ray_origin[other_coordinate] - line.a[other_coordinate]) / distance_oc;
|
||||
Floating val_c = line.a[coordinate] + t * (line.b[coordinate] - line.a[coordinate]);
|
||||
if (ray_origin[coordinate] > val_c) {
|
||||
return { 1, 0 };
|
||||
} else if (ray_origin[coordinate] < val_c) {
|
||||
return { 0, 1 };
|
||||
} else { // ray origin is on boundary
|
||||
return { -1, -1 };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int intersections_above = 0;
|
||||
int intersections_below = 0;
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto& node_left = tree.node(left_node_idx);
|
||||
const auto& node_right = tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
if (node_left.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] && node_left.bbox.max()[other_coordinate] >= ray_origin[other_coordinate]) {
|
||||
auto [above, below] = coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, coordinate>(left_node_idx, tree, lines,
|
||||
ray_origin);
|
||||
if (above < 0 || below < 0)
|
||||
return { -1, -1 };
|
||||
intersections_above += above;
|
||||
intersections_below += below;
|
||||
}
|
||||
|
||||
if (node_right.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] && node_right.bbox.max()[other_coordinate] >= ray_origin[other_coordinate]) {
|
||||
auto [above, below] = coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, coordinate>(right_node_idx, tree, lines,
|
||||
ray_origin);
|
||||
if (above < 0 || below < 0)
|
||||
return { -1, -1 };
|
||||
intersections_above += above;
|
||||
intersections_below += below;
|
||||
}
|
||||
return { intersections_above, intersections_below };
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LineType, typename TreeType, typename VectorType>
|
||||
inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(size_t node_idx,
|
||||
const TreeType& tree,
|
||||
const std::vector<LineType>& lines,
|
||||
const LineType& line,
|
||||
const typename TreeType::BoundingBox& line_bb)
|
||||
{
|
||||
const auto& node = tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf()) {
|
||||
VectorType intersection_pt;
|
||||
if (line_alg::intersection(line, lines[node.idx], &intersection_pt)) {
|
||||
return { std::pair<VectorType, size_t>(intersection_pt, node.idx) };
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto& node_left = tree.node(left_node_idx);
|
||||
const auto& node_right = tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
std::vector<std::pair<VectorType, size_t>> result;
|
||||
|
||||
if (node_left.bbox.intersects(line_bb)) {
|
||||
std::vector<std::pair<VectorType, size_t>> intersections = get_intersections_with_line<LineType, TreeType, VectorType>(left_node_idx, tree, lines, line, line_bb);
|
||||
result.insert(result.end(), intersections.begin(), intersections.end());
|
||||
}
|
||||
|
||||
if (node_right.bbox.intersects(line_bb)) {
|
||||
std::vector<std::pair<VectorType, size_t>> intersections = get_intersections_with_line<LineType, TreeType, VectorType>(right_node_idx, tree, lines, line, line_bb);
|
||||
result.insert(result.end(), intersections.begin(), intersections.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Build a balanced AABB Tree over a vector of lines, balancing the tree
|
||||
// on centroids of the lines.
|
||||
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
|
||||
// during tree traversal.
|
||||
template <typename LineType>
|
||||
inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector<LineType>& lines)
|
||||
{
|
||||
using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>;
|
||||
// using CoordType = typename TreeType::CoordType;
|
||||
using VectorType = typename TreeType::VectorType;
|
||||
using BoundingBox = typename TreeType::BoundingBox;
|
||||
|
||||
struct InputType {
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
const VectorType& centroid() const { return m_centroid; }
|
||||
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
VectorType m_centroid;
|
||||
};
|
||||
|
||||
std::vector<InputType> input;
|
||||
input.reserve(lines.size());
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
const LineType& line = lines[i];
|
||||
InputType n;
|
||||
n.m_idx = i;
|
||||
n.m_centroid = (line.a + line.b) * 0.5;
|
||||
n.m_bbox = BoundingBox(line.a, line.a);
|
||||
n.m_bbox.extend(line.b);
|
||||
input.emplace_back(n);
|
||||
}
|
||||
|
||||
TreeType out;
|
||||
out.build(std::move(input));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Finding a closest line, its closest point and squared distance to the closest point
|
||||
// Returns squared distance to the closest point or -1 if the input is empty.
|
||||
// or no closer point than max_sq_dist
|
||||
template <typename LineType, typename TreeType, typename VectorType>
|
||||
inline typename VectorType::Scalar squared_distance_to_indexed_lines(
|
||||
const std::vector<LineType>& lines,
|
||||
const TreeType& tree,
|
||||
const VectorType& point,
|
||||
size_t& hit_idx_out,
|
||||
Eigen::PlainObjectBase<VectorType>& hit_point_out,
|
||||
typename VectorType::Scalar max_sqr_dist = std::numeric_limits<typename VectorType::Scalar>::infinity())
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
if (tree.empty())
|
||||
return Scalar(-1);
|
||||
auto distancer = detail::IndexedLinesDistancer<LineType, TreeType, VectorType> { lines, tree, point };
|
||||
return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_sqr_dist,
|
||||
hit_idx_out, hit_point_out);
|
||||
}
|
||||
|
||||
// Returns all lines within the given radius limit
|
||||
template <typename LineType, typename TreeType, typename VectorType>
|
||||
inline std::vector<size_t> all_lines_in_radius(const std::vector<LineType>& lines,
|
||||
const TreeType& tree,
|
||||
const VectorType& point,
|
||||
typename VectorType::Scalar max_distance_squared)
|
||||
{
|
||||
auto distancer = detail::IndexedLinesDistancer<LineType, TreeType, VectorType> { lines, tree, point };
|
||||
|
||||
if (tree.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<size_t> found_lines {};
|
||||
AABBTreeIndirect::detail::indexed_primitives_within_distance_squared_recurisve(distancer, size_t(0), max_distance_squared, found_lines);
|
||||
return found_lines;
|
||||
}
|
||||
|
||||
|
||||
// return 1 if true, -1 if false, 0 for point on contour (or if cannot be determined)
|
||||
template <typename LineType, typename TreeType, typename VectorType>
|
||||
inline int point_outside_closed_contours(const std::vector<LineType>& lines, const TreeType& tree, const VectorType& point)
|
||||
{
|
||||
if (tree.empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, 0>(0, tree, lines, point);
|
||||
if (hits_above < 0 || hits_below < 0) {
|
||||
return 0;
|
||||
} else if (hits_above % 2 == 1 && hits_below % 2 == 1) {
|
||||
return -1;
|
||||
} else if (hits_above % 2 == 0 && hits_below % 2 == 0) {
|
||||
return 1;
|
||||
} else { // this should not happen with closed contours. lets check it in Y direction
|
||||
auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, 1>(0, tree, lines, point);
|
||||
if (hits_above < 0 || hits_below < 0) {
|
||||
return 0;
|
||||
} else if (hits_above % 2 == 1 && hits_below % 2 == 1) {
|
||||
return -1;
|
||||
} else if (hits_above % 2 == 0 && hits_below % 2 == 0) {
|
||||
return 1;
|
||||
} else { // both results were unclear
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool sorted, typename VectorType, typename LineType, typename TreeType>
|
||||
inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(const std::vector<LineType>& lines,
|
||||
const TreeType& tree,
|
||||
const LineType& line)
|
||||
{
|
||||
if (tree.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto line_bb = typename TreeType::BoundingBox(line.a, line.a);
|
||||
line_bb.extend(line.b);
|
||||
|
||||
auto intersections = detail::get_intersections_with_line<LineType, TreeType, VectorType>(0, tree, lines, line, line_bb);
|
||||
if (sorted) {
|
||||
using Floating =
|
||||
typename std::conditional<std::is_floating_point<typename LineType::Scalar>::value, typename LineType::Scalar, double>::type;
|
||||
|
||||
std::vector<std::pair<Floating, std::pair<VectorType, size_t>>> points_with_sq_distance {};
|
||||
for (const auto& p : intersections) {
|
||||
points_with_sq_distance.emplace_back((p.first - line.a).template cast<Floating>().squaredNorm(), p);
|
||||
}
|
||||
std::sort(points_with_sq_distance.begin(), points_with_sq_distance.end(),
|
||||
[](const std::pair<Floating, std::pair<VectorType, size_t>>& left,
|
||||
std::pair<Floating, std::pair<VectorType, size_t>>& right) { return left.first < right.first; });
|
||||
for (size_t i = 0; i < points_with_sq_distance.size(); i++) {
|
||||
intersections[i] = points_with_sq_distance[i].second;
|
||||
}
|
||||
}
|
||||
|
||||
return intersections;
|
||||
}
|
||||
|
||||
template <typename LineType>
|
||||
class LinesDistancer {
|
||||
public:
|
||||
using Scalar = typename LineType::Scalar;
|
||||
using Floating = typename std::conditional<std::is_floating_point<Scalar>::value, Scalar, double>::type;
|
||||
|
||||
private:
|
||||
std::vector<LineType> lines;
|
||||
AABBTreeIndirect::Tree<2, Scalar> tree;
|
||||
|
||||
public:
|
||||
explicit LinesDistancer(const std::vector<LineType>& lines)
|
||||
: lines(lines)
|
||||
{
|
||||
tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines);
|
||||
}
|
||||
|
||||
explicit LinesDistancer(std::vector<LineType>&& lines)
|
||||
: lines(lines)
|
||||
{
|
||||
tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines);
|
||||
}
|
||||
|
||||
LinesDistancer() = default;
|
||||
|
||||
// 1 true, -1 false, 0 cannot determine
|
||||
int outside(const Vec<2, Scalar>& point) const { return point_outside_closed_contours(lines, tree, point); }
|
||||
|
||||
// negative sign means inside
|
||||
template <bool SIGNED_DISTANCE>
|
||||
std::tuple<Floating, size_t, Vec<2, Floating>> distance_from_lines_extra(const Vec<2, Scalar>& point) const
|
||||
{
|
||||
size_t nearest_line_index_out = size_t(-1);
|
||||
Vec<2, Floating> nearest_point_out = Vec<2, Floating>::Zero();
|
||||
Vec<2, Floating> p = point.template cast<Floating>();
|
||||
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, nearest_line_index_out, nearest_point_out);
|
||||
|
||||
if (distance < 0) {
|
||||
return { std::numeric_limits<Floating>::infinity(), nearest_line_index_out, nearest_point_out };
|
||||
}
|
||||
distance = sqrt(distance);
|
||||
|
||||
if (SIGNED_DISTANCE) {
|
||||
distance *= outside(point);
|
||||
}
|
||||
|
||||
return { distance, nearest_line_index_out, nearest_point_out };
|
||||
}
|
||||
|
||||
template <bool SIGNED_DISTANCE>
|
||||
Floating distance_from_lines(const Vec<2, typename LineType::Scalar>& point) const
|
||||
{
|
||||
auto [dist, idx, np] = distance_from_lines_extra<SIGNED_DISTANCE>(point);
|
||||
return dist;
|
||||
}
|
||||
|
||||
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
|
||||
{
|
||||
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
|
||||
}
|
||||
|
||||
template <bool sorted>
|
||||
std::vector<std::pair<Vec<2, Scalar>, size_t>> intersections_with_line(const LineType& line) const
|
||||
{
|
||||
return get_intersections_with_line<sorted, Vec<2, Scalar>>(lines, tree, line);
|
||||
}
|
||||
|
||||
const LineType& get_line(size_t line_idx) const { return lines[line_idx]; }
|
||||
|
||||
const std::vector<LineType>& get_lines() const { return lines; }
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Slic3r::AABBTreeLines
|
||||
|
||||
#endif /* SRC_LIBSLIC3R_AABBTREELINES_HPP_ */
|
||||
@@ -0,0 +1,157 @@
|
||||
#ifndef ASTAR_HPP
|
||||
#define ASTAR_HPP
|
||||
|
||||
#include <cmath> // std::isinf() is here
|
||||
#include <unordered_map>
|
||||
|
||||
#include "libslic3r/MutablePriorityQueue.hpp"
|
||||
|
||||
namespace Slic3r { namespace astar {
|
||||
|
||||
// Borrowed from C++20
|
||||
template<class T> using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
|
||||
// Input interface for the Astar algorithm. Specialize this struct for a
|
||||
// particular type and implement all the 4 methods and specify the Node type
|
||||
// to register the new type for the astar implementation.
|
||||
template<class T> struct TracerTraits_
|
||||
{
|
||||
// The type of a node used by this tracer. Usually a point in space.
|
||||
using Node = typename T::Node;
|
||||
|
||||
// Call fn for every new node reachable from node 'src'. fn should have the
|
||||
// candidate node as its only argument.
|
||||
template<class Fn> static void foreach_reachable(const T &tracer, const Node &src, Fn &&fn) { tracer.foreach_reachable(src, fn); }
|
||||
|
||||
// Get the distance from node 'a' to node 'b'. This is sometimes referred
|
||||
// to as the g value of a node in AStar context.
|
||||
static float distance(const T &tracer, const Node &a, const Node &b) { return tracer.distance(a, b); }
|
||||
|
||||
// Get the estimated distance heuristic from node 'n' to the destination.
|
||||
// This is referred to as the h value in AStar context.
|
||||
// If node 'n' is the goal, this function should return a negative value.
|
||||
// Note that this heuristic should be admissible (never bigger than the real
|
||||
// cost) in order for Astar to work.
|
||||
static float goal_heuristic(const T &tracer, const Node &n) { return tracer.goal_heuristic(n); }
|
||||
|
||||
// Return a unique identifier (hash) for node 'n'.
|
||||
static size_t unique_id(const T &tracer, const Node &n) { return tracer.unique_id(n); }
|
||||
};
|
||||
|
||||
// Helper definition to get the node type of a tracer
|
||||
template<class T> using TracerNodeT = typename TracerTraits_<remove_cvref_t<T>>::Node;
|
||||
|
||||
constexpr auto Unassigned = std::numeric_limits<size_t>::max();
|
||||
|
||||
template<class Tracer> struct QNode // Queue node. Keeps track of scores g, and h
|
||||
{
|
||||
TracerNodeT<Tracer> node; // The actual node itself
|
||||
size_t queue_id; // Position in the open queue or Unassigned if closed
|
||||
size_t parent; // unique id of the parent or Unassigned
|
||||
|
||||
float g, h;
|
||||
float f() const { return g + h; }
|
||||
|
||||
QNode(TracerNodeT<Tracer> n = {}, size_t p = Unassigned, float gval = std::numeric_limits<float>::infinity(), float hval = 0.f)
|
||||
: node{std::move(n)}, parent{p}, queue_id{InvalidQueueID}, g{gval}, h{hval}
|
||||
{}
|
||||
};
|
||||
|
||||
// Run the AStar algorithm on a tracer implementation.
|
||||
// The 'tracer' argument encapsulates the domain (grid, point cloud, etc...)
|
||||
// The 'source' argument is the starting node.
|
||||
// The 'out' argument is the output iterator into which the output nodes are
|
||||
// written. For performance reasons, the order is reverse, from the destination
|
||||
// to the source -- (destination included, source is not).
|
||||
// The 'cached_nodes' argument is an optional associative container to hold a
|
||||
// QNode entry for each visited node. Any compatible container can be used
|
||||
// (like std::map or maps with different allocators, even a sufficiently large
|
||||
// std::vector).
|
||||
//
|
||||
// Note that no destination node is given in the signature. The tracer's
|
||||
// goal_heuristic() method should return a negative value if a node is a
|
||||
// destination node.
|
||||
template<class Tracer, class It, class NodeMap = std::unordered_map<size_t, QNode<Tracer>>>
|
||||
bool search_route(const Tracer &tracer, const TracerNodeT<Tracer> &source, It out, NodeMap &&cached_nodes = {})
|
||||
{
|
||||
using Node = TracerNodeT<Tracer>;
|
||||
using QNode = QNode<Tracer>;
|
||||
using TracerTraits = TracerTraits_<remove_cvref_t<Tracer>>;
|
||||
|
||||
struct LessPred
|
||||
{ // Comparison functor needed by the priority queue
|
||||
NodeMap &m;
|
||||
bool operator()(size_t node_a, size_t node_b) { return m[node_a].f() < m[node_b].f(); }
|
||||
};
|
||||
|
||||
auto qopen = make_mutable_priority_queue<size_t, true>([&cached_nodes](size_t el, size_t qidx) { cached_nodes[el].queue_id = qidx; }, LessPred{cached_nodes});
|
||||
|
||||
QNode initial{source, /*parent = */ Unassigned, /*g = */ 0.f};
|
||||
size_t source_id = TracerTraits::unique_id(tracer, source);
|
||||
cached_nodes[source_id] = initial;
|
||||
qopen.push(source_id);
|
||||
|
||||
size_t goal_id = TracerTraits::goal_heuristic(tracer, source) < 0.f ? source_id : Unassigned;
|
||||
|
||||
while (goal_id == Unassigned && !qopen.empty()) {
|
||||
size_t q_id = qopen.top();
|
||||
qopen.pop();
|
||||
QNode &q = cached_nodes[q_id];
|
||||
|
||||
// This should absolutely be initialized in the cache already
|
||||
assert(!std::isinf(q.g));
|
||||
|
||||
TracerTraits::foreach_reachable(tracer, q.node, [&](const Node &succ_nd) {
|
||||
if (goal_id != Unassigned) return true;
|
||||
|
||||
float h = TracerTraits::goal_heuristic(tracer, succ_nd);
|
||||
float dst = TracerTraits::distance(tracer, q.node, succ_nd);
|
||||
size_t succ_id = TracerTraits::unique_id(tracer, succ_nd);
|
||||
QNode qsucc_nd{succ_nd, q_id, q.g + dst, h};
|
||||
|
||||
if (h < 0.f) {
|
||||
goal_id = succ_id;
|
||||
cached_nodes[succ_id] = qsucc_nd;
|
||||
} else {
|
||||
// If succ_id is not in cache, it gets created with g = infinity
|
||||
QNode &prev_nd = cached_nodes[succ_id];
|
||||
|
||||
if (qsucc_nd.g < prev_nd.g) {
|
||||
// new route is better, apply it:
|
||||
|
||||
// Save the old queue id, it would be lost after the next line
|
||||
size_t queue_id = prev_nd.queue_id;
|
||||
|
||||
// The cache needs to be updated either way
|
||||
prev_nd = qsucc_nd;
|
||||
|
||||
if (queue_id == InvalidQueueID)
|
||||
// was in closed or unqueued, rescheduling
|
||||
qopen.push(succ_id);
|
||||
else // was in open, updating
|
||||
qopen.update(queue_id);
|
||||
}
|
||||
}
|
||||
|
||||
return goal_id != Unassigned;
|
||||
});
|
||||
}
|
||||
|
||||
// Write the output, do not reverse. Clients can do so if they need to.
|
||||
if (goal_id != Unassigned) {
|
||||
const QNode *q = &cached_nodes[goal_id];
|
||||
while (q->parent != Unassigned) {
|
||||
assert(!std::isinf(q->g)); // Uninitialized nodes are NOT allowed
|
||||
|
||||
*out = q->node;
|
||||
++out;
|
||||
q = &cached_nodes[q->parent];
|
||||
}
|
||||
}
|
||||
|
||||
return goal_id != Unassigned;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::astar
|
||||
|
||||
#endif // ASTAR_HPP
|
||||
@@ -0,0 +1,317 @@
|
||||
#include "LineSplit.hpp"
|
||||
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
//#define DEBUG_SPLIT_LINE
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
#ifdef DEBUG_SPLIT_LINE
|
||||
static std::atomic<std::uint32_t> g_dbg_id = 0;
|
||||
#endif
|
||||
|
||||
// Z for points from clip polygon
|
||||
static constexpr auto CLIP_IDX = std::numeric_limits<ClipperLib_Z::cInt>::max();
|
||||
|
||||
static void cb_split_line(const ClipperZUtils::ZPoint& e1bot,
|
||||
const ClipperZUtils::ZPoint& e1top,
|
||||
const ClipperZUtils::ZPoint& e2bot,
|
||||
const ClipperZUtils::ZPoint& e2top,
|
||||
ClipperZUtils::ZPoint& pt)
|
||||
{
|
||||
coord_t zs[4]{e1bot.z(), e1top.z(), e2bot.z(), e2top.z()};
|
||||
std::sort(zs, zs + 4);
|
||||
pt.z() = -(zs[0] + 1);
|
||||
}
|
||||
|
||||
static bool is_src(const ClipperZUtils::ZPoint& p) { return p.z() >= 0 && p.z() != CLIP_IDX; }
|
||||
static bool is_clip(const ClipperZUtils::ZPoint& p) { return p.z() == CLIP_IDX; }
|
||||
static bool is_new(const ClipperZUtils::ZPoint& p) { return p.z() < 0; }
|
||||
static size_t to_src_idx(const ClipperZUtils::ZPoint& p)
|
||||
{
|
||||
assert(!is_clip(p));
|
||||
if (is_src(p)) {
|
||||
return p.z();
|
||||
} else {
|
||||
return -p.z() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static Point to_point(const ClipperZUtils::ZPoint& p) { return {p.x(), p.y()}; }
|
||||
|
||||
using SplitNode = std::vector<ClipperZUtils::ZPath*>;
|
||||
|
||||
// Note: p cannot be one of the line end
|
||||
static bool point_on_line(const Point& p, const Line& l)
|
||||
{
|
||||
// Check collinear
|
||||
const Vec2crd d1 = l.b - l.a;
|
||||
const Vec2crd d2 = p - l.a;
|
||||
if (d1.x() * d2.y() != d1.y() * d2.x()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure p is in between line.a and line.b
|
||||
if (l.a.x() != l.b.x())
|
||||
return (p.x() > l.a.x()) == (p.x() < l.b.x());
|
||||
else
|
||||
return (p.y() > l.a.y()) == (p.y() < l.b.y());
|
||||
}
|
||||
|
||||
SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& clip, bool closed)
|
||||
{
|
||||
assert(path.size() > 1);
|
||||
#ifdef DEBUG_SPLIT_LINE
|
||||
const auto dbg_path_points = ClipperZUtils::from_zpath<false>(path);
|
||||
BoundingBox dbg_bbox = get_extents(clip);
|
||||
dbg_bbox.merge(get_extents(dbg_path_points));
|
||||
dbg_bbox.offset(scale_(1.));
|
||||
const std::uint32_t dbg_id = g_dbg_id++;
|
||||
{
|
||||
::Slic3r::SVG svg(debug_out_path("do_split_line_%d_input.svg", dbg_id).c_str(), dbg_bbox);
|
||||
svg.draw(clip, "red", 0.5);
|
||||
svg.draw_outline(clip, "red");
|
||||
svg.draw(Polyline{dbg_path_points});
|
||||
svg.draw(dbg_path_points);
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
ClipperZUtils::ZPaths intersections;
|
||||
// Perform an intersection
|
||||
{
|
||||
// Convert clip polygon to closed contours
|
||||
ClipperZUtils::ZPaths clip_path;
|
||||
for (const auto& exp : clip) {
|
||||
clip_path.emplace_back(ClipperZUtils::to_zpath<false>(exp.contour.points, CLIP_IDX));
|
||||
for (const Polygon& hole : exp.holes)
|
||||
clip_path.emplace_back(ClipperZUtils::to_zpath<false>(hole.points, CLIP_IDX));
|
||||
}
|
||||
|
||||
ClipperLib_Z::Clipper zclipper;
|
||||
zclipper.PreserveCollinear(true);
|
||||
zclipper.ZFillFunction(cb_split_line);
|
||||
zclipper.AddPaths(clip_path, ClipperLib_Z::ptClip, true);
|
||||
zclipper.AddPath(path, ClipperLib_Z::ptSubject, false);
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), intersections);
|
||||
}
|
||||
if (intersections.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SPLIT_LINE
|
||||
{
|
||||
int i = 0;
|
||||
for (const auto& segment : intersections) {
|
||||
::Slic3r::SVG svg(debug_out_path("do_split_line_%d_seg_%d.svg", dbg_id, i).c_str(), dbg_bbox);
|
||||
svg.draw(clip, "red", 0.5);
|
||||
svg.draw_outline(clip, "red");
|
||||
const auto segment_points = ClipperZUtils::from_zpath<false>(segment);
|
||||
svg.draw(Polyline{segment_points});
|
||||
for (const ClipperZUtils::ZPoint& p : segment) {
|
||||
const auto z = p.z();
|
||||
if (is_new(p)) {
|
||||
svg.draw(to_point(p), "yellow");
|
||||
} else if (is_clip(p)) {
|
||||
svg.draw(to_point(p), "red");
|
||||
} else {
|
||||
svg.draw(to_point(p), "black");
|
||||
}
|
||||
}
|
||||
svg.Close();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Connect the intersection back to the remaining loop
|
||||
std::vector<SplitNode> split_chain;
|
||||
{
|
||||
// AABBTree over source paths.
|
||||
// Only built if necessary, that is if any of the clipped segment has first point came from clip polygon,
|
||||
// and we need to find out which source edge that point came from.
|
||||
AABBTreeLines::LinesDistancer<Line> aabb_tree;
|
||||
const auto resolve_clip_point = [&path, &aabb_tree](ClipperZUtils::ZPoint& zp) {
|
||||
if (!is_clip(zp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aabb_tree.get_lines().empty()) {
|
||||
Lines lines;
|
||||
lines.reserve(path.size() - 1);
|
||||
for (auto it = path.begin() + 1; it != path.end(); ++it) {
|
||||
lines.emplace_back(to_point(it[-1]), to_point(*it));
|
||||
}
|
||||
aabb_tree = AABBTreeLines::LinesDistancer(lines);
|
||||
}
|
||||
|
||||
const Point p = to_point(zp);
|
||||
const auto possible_edges = aabb_tree.all_lines_in_radius(p, SCALED_EPSILON);
|
||||
assert(!possible_edges.empty());
|
||||
for (const size_t l : possible_edges) {
|
||||
// Check if the point is on the line
|
||||
const Line line(to_point(path[l]), to_point(path[l + 1]));
|
||||
if (p == line.a) {
|
||||
zp.z() = path[l].z();
|
||||
break;
|
||||
}
|
||||
if (p == line.b) {
|
||||
zp.z() = path[l + 1].z();
|
||||
break;
|
||||
}
|
||||
if (point_on_line(p, line)) {
|
||||
zp.z() = -(path[l].z() + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_clip(zp)) {
|
||||
// Too bad! Couldn't find the src edge, so we just pick the first one and hope it works
|
||||
zp.z() = -(path[possible_edges[0]].z() + 1);
|
||||
}
|
||||
};
|
||||
|
||||
split_chain.assign(path.size(), {});
|
||||
for (ClipperZUtils::ZPath& segment : intersections) {
|
||||
assert(segment.size() >= 2);
|
||||
// Resolve all clip points
|
||||
std::for_each(segment.begin(), segment.end(), resolve_clip_point);
|
||||
|
||||
// Ensure the point order in segment
|
||||
std::sort(segment.begin(), segment.end(), [&path](const ClipperZUtils::ZPoint& a, const ClipperZUtils::ZPoint& b) -> bool {
|
||||
if (is_new(a) && is_new(b) && a.z() == b.z()) {
|
||||
// Make sure a point is closer to the src point than b
|
||||
const auto src = to_point(path[-a.z() - 1]);
|
||||
return (to_point(a) - src).squaredNorm() < (to_point(b) - src).squaredNorm();
|
||||
}
|
||||
const auto a_idx = to_src_idx(a);
|
||||
const auto b_idx = to_src_idx(b);
|
||||
if (a_idx == b_idx) {
|
||||
// On same line, prefer the src point first
|
||||
return is_src(a);
|
||||
} else {
|
||||
return a_idx < b_idx;
|
||||
}
|
||||
});
|
||||
|
||||
// Chain segment back to the original path
|
||||
ClipperZUtils::ZPoint& front = segment.front();
|
||||
const ClipperZUtils::ZPoint* previous_src_point = nullptr;
|
||||
if (is_src(front)) {
|
||||
// The segment starts with a point from src path, which means apart from the last point,
|
||||
// all other points on this segment should come from the src path or the clip polygon
|
||||
|
||||
// Connect the segment to the src path
|
||||
auto& node = split_chain[front.z()];
|
||||
node.insert(node.begin(), &segment);
|
||||
|
||||
previous_src_point = &front;
|
||||
} else if (is_new(front)) {
|
||||
const auto id = -front.z() - 1; // Get the src path index
|
||||
const ClipperZUtils::ZPoint& src_p = path[id]; // Get the corresponding src point
|
||||
const auto dist2 = (front - src_p).block<2, 1>(0,0).squaredNorm(); // Distance between the src point and current point
|
||||
// Find the place on the src line that current point should lie on
|
||||
auto& node = split_chain[id];
|
||||
auto it = std::find_if(node.begin(), node.end(), [dist2, &src_p](const ClipperZUtils::ZPath* p) {
|
||||
const ClipperZUtils::ZPoint& p_front = p->front();
|
||||
if (is_src(p_front)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto dist2_2 = (p_front - src_p).block<2, 1>(0, 0).squaredNorm();
|
||||
return dist2_2 > dist2;
|
||||
});
|
||||
// Insert this split
|
||||
node.insert(it, &segment);
|
||||
|
||||
previous_src_point = &src_p;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Once we figured out the start point, we can then normalize the remaining points on the segment
|
||||
for (ClipperZUtils::ZPoint& p : segment) {
|
||||
assert(!is_new(p) || p == front || p == segment.back()); // Only the first and last point can be a new intersection
|
||||
if (is_src(p)) {
|
||||
previous_src_point = &p;
|
||||
} else if (is_clip(p)) {
|
||||
// Treat point from clip polygon as new point
|
||||
p.z() = -(previous_src_point->z() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we reconstruct the final path by connecting splits
|
||||
SplittedLine result;
|
||||
size_t idx = 0;
|
||||
while (idx < split_chain.size()) {
|
||||
const ClipperZUtils::ZPoint& p = path[idx];
|
||||
const auto& node = split_chain[idx];
|
||||
if (node.empty()) {
|
||||
result.emplace_back(to_point(p), false, idx);
|
||||
idx++;
|
||||
} else {
|
||||
if (!is_src(node.front()->front())) {
|
||||
if (result.empty() || result.back().get_src_index() != to_src_idx(p)) {
|
||||
//const auto& last = result.back();
|
||||
//if (result.empty() || last.get_src_index() != to_src_idx(p)) {
|
||||
result.emplace_back(to_point(p), false, idx);
|
||||
}
|
||||
}
|
||||
for (const auto segment : node) {
|
||||
for (const ClipperZUtils::ZPoint& sp : *segment) {
|
||||
assert(!is_clip(sp));
|
||||
result.emplace_back(to_point(sp), true, sp.z());
|
||||
}
|
||||
result.back().clipped = false; // Mark the end of the clipped line
|
||||
}
|
||||
|
||||
// Determine the next start point
|
||||
const auto back = result.back().src_idx;
|
||||
if (back < 0) {
|
||||
auto next_idx = -back - 1;
|
||||
if (next_idx == idx) {
|
||||
next_idx++;
|
||||
} else if (split_chain[next_idx].empty()) {
|
||||
next_idx++;
|
||||
}
|
||||
idx = next_idx;
|
||||
} else {
|
||||
result.pop_back();
|
||||
idx = back;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef DEBUG_SPLIT_LINE
|
||||
{
|
||||
::Slic3r::SVG svg(debug_out_path("do_split_line_%d_result.svg", dbg_id).c_str(), dbg_bbox);
|
||||
svg.draw(clip, "red", 0.5);
|
||||
svg.draw_outline(clip, "red");
|
||||
for (auto it = result.begin() + 1; it != result.end(); ++it) {
|
||||
const auto& a = *(it - 1);
|
||||
const auto& b = *it;
|
||||
const bool clipped = a.clipped;
|
||||
const Line l(a.p, b.p);
|
||||
svg.draw(l, clipped ? "yellow" : "black");
|
||||
}
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (closed) {
|
||||
// Remove last point which was duplicated
|
||||
result.pop_back();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_
|
||||
#define SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_
|
||||
|
||||
#include "ClipperZUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
struct SplitLineJunction
|
||||
{
|
||||
Point p;
|
||||
|
||||
// true if the line between this point and the next point is inside the clip polygon (or on the edge of the clip polygon)
|
||||
bool clipped;
|
||||
|
||||
// Index from the original input.
|
||||
// - If this junction is presented in the source polygon/polyline, this is the index of the point with in the source;
|
||||
// - if this point in a new point that caused by the intersection, this will be -(1+index of the first point of the source line involved in this intersection);
|
||||
// - if this junction came from the clip polygon, it will be treated as new point.
|
||||
int64_t src_idx;
|
||||
|
||||
SplitLineJunction(const Point& p, bool clipped, int64_t src_idx)
|
||||
: p(p)
|
||||
, clipped(clipped)
|
||||
, src_idx(src_idx) {}
|
||||
|
||||
bool is_src() const { return src_idx >= 0; }
|
||||
size_t get_src_index() const
|
||||
{
|
||||
if (is_src()) {
|
||||
return src_idx;
|
||||
} else {
|
||||
return -src_idx - 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using SplittedLine = std::vector<SplitLineJunction>;
|
||||
|
||||
SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& clip, bool closed);
|
||||
|
||||
// Return the splitted line, or empty if no intersection found
|
||||
template<class PathType>
|
||||
SplittedLine split_line(const PathType& path, const ExPolygons& clip, bool closed)
|
||||
{
|
||||
if (path.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Convert the input path into an open ZPath
|
||||
ClipperZUtils::ZPath p;
|
||||
p.reserve(path.size() + closed ? 1 : 0);
|
||||
ClipperLib_Z::cInt z = 0;
|
||||
for (const auto& point : path) {
|
||||
p.emplace_back(point.x(), point.y(), z);
|
||||
z++;
|
||||
}
|
||||
if (closed) {
|
||||
// duplicate the first point at the end to make a closed path open
|
||||
p.emplace_back(p.front());
|
||||
p.back().z() = z;
|
||||
}
|
||||
|
||||
return do_split_line(p, clip, closed);
|
||||
}
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_ */
|
||||
@@ -0,0 +1,563 @@
|
||||
#include "RegionExpansion.hpp"
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/ClipperZUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta)
|
||||
inline double clipper_round_offset_error(double offset, double arc_tolerance)
|
||||
{
|
||||
static constexpr const double def_arc_tolerance = 0.25;
|
||||
const double y =
|
||||
arc_tolerance <= 0 ?
|
||||
def_arc_tolerance :
|
||||
arc_tolerance > offset * def_arc_tolerance ?
|
||||
offset * def_arc_tolerance :
|
||||
arc_tolerance;
|
||||
double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI);
|
||||
return offset * (1. - cos(M_PI / steps));
|
||||
}
|
||||
|
||||
RegionExpansionParameters RegionExpansionParameters::build(
|
||||
// Scaled expansion value
|
||||
float full_expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_expansion_steps)
|
||||
{
|
||||
assert(full_expansion > 0);
|
||||
assert(expansion_step > 0);
|
||||
assert(max_nr_expansion_steps > 0);
|
||||
|
||||
RegionExpansionParameters out;
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
// The expansion should not be too tiny, but also small enough, so the following expansion will
|
||||
// compensate for tiny_expansion and bring the wave back to the boundary without producing
|
||||
// ugly cusps where it touches the boundary.
|
||||
out.tiny_expansion = std::min(0.25f * full_expansion, scaled<float>(0.05f));
|
||||
size_t nsteps = size_t(ceil((full_expansion - out.tiny_expansion) / expansion_step));
|
||||
if (max_nr_expansion_steps > 0)
|
||||
nsteps = std::min(nsteps, max_nr_expansion_steps);
|
||||
assert(nsteps > 0);
|
||||
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
|
||||
if (nsteps > 1 && 0.25 * out.initial_step < out.tiny_expansion) {
|
||||
// Decrease the step size by lowering number of steps.
|
||||
nsteps = std::max<size_t>(1, (floor((full_expansion - out.tiny_expansion) / (4. * out.tiny_expansion))));
|
||||
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
|
||||
}
|
||||
if (0.25 * out.initial_step < out.tiny_expansion || nsteps == 1) {
|
||||
out.tiny_expansion = 0.2f * full_expansion;
|
||||
out.initial_step = 0.8f * full_expansion;
|
||||
}
|
||||
out.other_step = out.initial_step;
|
||||
out.num_other_steps = nsteps - 1;
|
||||
|
||||
// Accuracy of the offsetter for wave propagation.
|
||||
out.arc_tolerance = scaled<double>(0.1);
|
||||
out.shortest_edge_length = out.initial_step * ClipperOffsetShortestEdgeFactor;
|
||||
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation. Needs to be in sync with the offsetter accuracy.
|
||||
// Clipper positive round offset should rather offset less than more.
|
||||
// Still a little bit of additional offset was added.
|
||||
out.max_inflation = (out.tiny_expansion + nsteps * out.initial_step) * 1.1;
|
||||
// (clipper_round_offset_error(out.tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(out.initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath.
|
||||
// The expanded contours are then opened (the first point is repeated at the end).
|
||||
static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened(
|
||||
const ExPolygons &src, const float expansion, coord_t &base_idx)
|
||||
{
|
||||
ClipperLib_Z::Paths out;
|
||||
out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
ClipperLib::ClipperOffset offsetter;
|
||||
offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor;
|
||||
ClipperLib::Paths expansion_cache;
|
||||
for (const ExPolygon &expoly : src) {
|
||||
for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) {
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
offsetter.Clear();
|
||||
offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon);
|
||||
expansion_cache.clear();
|
||||
offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion);
|
||||
append(out, ClipperZUtils::to_zpaths<true>(expansion_cache, base_idx));
|
||||
}
|
||||
++ base_idx;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Paths were created by splitting closed polygons into open paths and then by clipping them.
|
||||
// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons.
|
||||
// Those ends are sorted lexicographically in "splits".
|
||||
// Reconnect those split pieces.
|
||||
static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector<std::pair<ClipperLib_Z::IntPoint, int>> &splits)
|
||||
{
|
||||
for (auto it_path = paths.begin(); it_path != paths.end(); ) {
|
||||
ClipperLib_Z::Path &path = *it_path;
|
||||
assert(path.size() >= 2);
|
||||
bool merged = false;
|
||||
if (path.size() >= 2) {
|
||||
const ClipperLib_Z::IntPoint &front = path.front();
|
||||
const ClipperLib_Z::IntPoint &back = path.back();
|
||||
// The path before clipping was supposed to cross the clipping boundary or be fully out of it.
|
||||
// Thus the clipped contour is supposed to become open, with one exception: The anchor expands into a closed hole.
|
||||
if (front.x() != back.x() || front.y() != back.y()) {
|
||||
// Look up the ends in "splits", possibly join the contours.
|
||||
// "splits" maps into the other piece connected to the same end point.
|
||||
auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair<ClipperLib_Z::IntPoint, int>* {
|
||||
auto it = std::lower_bound(splits.begin(), splits.end(), pt,
|
||||
[](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); });
|
||||
return it != splits.end() && it->first == pt ? &(*it) : nullptr;
|
||||
};
|
||||
auto *end = find_end(front);
|
||||
bool end_front = true;
|
||||
if (! end) {
|
||||
end_front = false;
|
||||
end = find_end(back);
|
||||
}
|
||||
if (end) {
|
||||
// This segment ends at a split point of the source closed contour before clipping.
|
||||
if (end->second == -1) {
|
||||
// Open end was found, not matched yet.
|
||||
end->second = int(it_path - paths.begin());
|
||||
} else {
|
||||
// Open end was found and matched with end->second
|
||||
ClipperLib_Z::Path &other_path = paths[end->second];
|
||||
polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front);
|
||||
if (std::next(it_path) == paths.end()) {
|
||||
paths.pop_back();
|
||||
break;
|
||||
}
|
||||
path = std::move(paths.back());
|
||||
paths.pop_back();
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! merged)
|
||||
++ it_path;
|
||||
}
|
||||
}
|
||||
|
||||
using AABBTreeBBoxes = AABBTreeIndirect::Tree<2, coord_t>;
|
||||
|
||||
static AABBTreeBBoxes build_aabb_tree_over_expolygons(const ExPolygons &expolygons)
|
||||
{
|
||||
// Calculate bounding boxes of internal slices.
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(expolygons.size());
|
||||
for (size_t i = 0; i < expolygons.size(); ++ i)
|
||||
bboxes.emplace_back(i, get_extents(expolygons[i].contour));
|
||||
// Build AABB tree over bounding boxes of boundary expolygons.
|
||||
AABBTreeBBoxes out;
|
||||
out.build_modify_input(bboxes);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int sample_in_expolygons(
|
||||
// AABB tree over boundary expolygons
|
||||
const AABBTreeBBoxes &aabb_tree,
|
||||
const ExPolygons &expolygons,
|
||||
const Point &sample)
|
||||
{
|
||||
int out = -1;
|
||||
AABBTreeIndirect::traverse(aabb_tree,
|
||||
[&sample](const AABBTreeBBoxes::Node &node) {
|
||||
return node.bbox.contains(sample);
|
||||
},
|
||||
[&expolygons, &sample, &out](const AABBTreeBBoxes::Node &node) {
|
||||
assert(node.is_leaf());
|
||||
assert(node.is_valid());
|
||||
if (expolygons[node.idx].contains(sample)) {
|
||||
out = int(node.idx);
|
||||
// Stop traversal.
|
||||
return false;
|
||||
}
|
||||
// Continue traversal.
|
||||
return true;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<WaveSeed> wave_seeds(
|
||||
// Source regions that are supposed to touch the boundary.
|
||||
const ExPolygons &src,
|
||||
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
|
||||
const ExPolygons &boundary,
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
float tiny_expansion,
|
||||
// Sort output by boundary ID and source ID.
|
||||
bool sorted)
|
||||
{
|
||||
assert(tiny_expansion > 0);
|
||||
|
||||
if (src.empty() || boundary.empty())
|
||||
return {};
|
||||
|
||||
using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection;
|
||||
using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections;
|
||||
|
||||
ClipperLib_Z::Paths segments;
|
||||
Intersections intersections;
|
||||
|
||||
coord_t idx_boundary_begin = 1;
|
||||
coord_t idx_boundary_end = idx_boundary_begin;
|
||||
coord_t idx_src_end;
|
||||
|
||||
{
|
||||
ClipperLib_Z::Clipper zclipper;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
zclipper.ZFillFunction(visitor.clipper_callback());
|
||||
// as closed contours
|
||||
zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true);
|
||||
// as open contours
|
||||
std::vector<std::pair<ClipperLib_Z::IntPoint, int>> zsrc_splits;
|
||||
{
|
||||
idx_src_end = idx_boundary_end;
|
||||
ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end);
|
||||
zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false);
|
||||
zsrc_splits.reserve(zsrc.size());
|
||||
for (const ClipperLib_Z::Path &path : zsrc) {
|
||||
assert(path.size() >= 2);
|
||||
assert(path.front() == path.back());
|
||||
zsrc_splits.emplace_back(path.front(), -1);
|
||||
}
|
||||
std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); });
|
||||
}
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), segments);
|
||||
merge_splits(segments, zsrc_splits);
|
||||
}
|
||||
|
||||
// AABBTree over bounding boxes of boundaries.
|
||||
// Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point
|
||||
// with the boundary and all Z coordinates of the closed contour point to the source contour.
|
||||
AABBTreeBBoxes aabb_tree;
|
||||
|
||||
// Sort paths into their respective islands.
|
||||
// Each src x boundary will be processed (wave expanded) independently.
|
||||
// Multiple pieces of a single src may intersect the same boundary.
|
||||
WaveSeeds out;
|
||||
out.reserve(segments.size());
|
||||
int iseed = 0;
|
||||
for (const ClipperLib_Z::Path &path : segments) {
|
||||
assert(path.size() >= 2);
|
||||
ClipperLib_Z::IntPoint front = path.front();
|
||||
ClipperLib_Z::IntPoint back = path.back();
|
||||
// Both ends of a seed segment are supposed to be inside a single boundary expolygon.
|
||||
// Thus as long as the seed contour is not closed, it should be open at a boundary point.
|
||||
assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) ||
|
||||
//(front.z() < 0 && back.z() < 0));
|
||||
// Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created.
|
||||
(front.z() < 0 || back.z() < 0));
|
||||
|
||||
if (front != back && front.z() >= 0 && back.z() >= 0) {
|
||||
// Very rare case when both endpoints intersect boundary ExPolygons in existing points.
|
||||
// So the ZFillFunction callback hasn't been called.
|
||||
continue;
|
||||
} else
|
||||
if (front == back && (front.z() < idx_boundary_end)) {
|
||||
// This should be a very rare exception.
|
||||
// See https://github.com/prusa3d/PrusaSlicer/issues/12469.
|
||||
// Segement is open, yet its first point seems to be part of boundary polygon.
|
||||
// Take the first point with src polygon index.
|
||||
for (const ClipperLib_Z::IntPoint &point : path) {
|
||||
if (point.z() >= idx_boundary_end) {
|
||||
front = point;
|
||||
back = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Intersection *intersection = nullptr;
|
||||
auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) {
|
||||
return is.first >= 1 && is.first < idx_boundary_end &&
|
||||
is.second >= idx_boundary_end && is.second < idx_src_end;
|
||||
};
|
||||
if (front.z() < 0) {
|
||||
const Intersection &is = intersections[- front.z() - 1];
|
||||
assert(intersection_point_valid(is));
|
||||
if (intersection_point_valid(is))
|
||||
intersection = &is;
|
||||
}
|
||||
if (! intersection && back.z() < 0) {
|
||||
const Intersection &is = intersections[- back.z() - 1];
|
||||
assert(intersection_point_valid(is));
|
||||
if (intersection_point_valid(is))
|
||||
intersection = &is;
|
||||
}
|
||||
if (intersection) {
|
||||
// The path intersects the boundary contour at least at one side.
|
||||
out.push_back({ uint32_t(intersection->second - idx_boundary_end), uint32_t(intersection->first - 1), ClipperZUtils::from_zpath(path) });
|
||||
} else {
|
||||
// This should be a closed contour.
|
||||
assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end);
|
||||
// Find a source boundary expolygon of one sample of this closed path.
|
||||
if (aabb_tree.empty())
|
||||
aabb_tree = build_aabb_tree_over_expolygons(boundary);
|
||||
int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y()));
|
||||
// Boundary that contains the sample point was found.
|
||||
assert(boundary_id >= 0);
|
||||
if (boundary_id >= 0)
|
||||
out.push_back({ uint32_t(front.z() - idx_boundary_end), uint32_t(boundary_id), ClipperZUtils::from_zpath(path) });
|
||||
}
|
||||
++ iseed;
|
||||
}
|
||||
|
||||
if (sorted)
|
||||
// Sort the seeds by their intersection boundary and source contour.
|
||||
std::sort(out.begin(), out.end(), lower_by_boundary_and_src);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
out.reserve(polylines.size());
|
||||
ClipperLib::Paths out_this;
|
||||
for (const ClipperLib::Path &path : polylines) {
|
||||
assert(path.size() >= 2);
|
||||
co.Clear();
|
||||
co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : ClipperLib::etOpenRound);
|
||||
co.Execute(out_this, offset);
|
||||
append(out, std::move(out_this));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Input polygons may consist of multiple expolygons, even nested expolygons.
|
||||
// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive
|
||||
// clipping operation, thus it is not being done here.
|
||||
static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
out.reserve(polygons.size());
|
||||
ClipperLib::Paths out_this;
|
||||
for (const ClipperLib::Path &polygon : polygons) {
|
||||
co.Clear();
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon);
|
||||
bool ccw = ClipperLib::Orientation(polygon);
|
||||
co.Execute(out_this, ccw ? offset : - offset);
|
||||
if (! ccw) {
|
||||
// Reverse the resulting contours.
|
||||
for (ClipperLib::Path &path : out_this)
|
||||
std::reverse(path.begin(), path.end());
|
||||
}
|
||||
append(out, std::move(out_this));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(wavefront, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true);
|
||||
ClipperLib::Paths out;
|
||||
clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive);
|
||||
return out;
|
||||
}
|
||||
|
||||
static Polygons propagate_wave_from_boundary(
|
||||
ClipperLib::ClipperOffset &co,
|
||||
// Seed of the wave: Open polylines very close to the boundary.
|
||||
const ClipperLib::Paths &seed,
|
||||
// Boundary inside which the waveform will propagate.
|
||||
const ExPolygon &boundary,
|
||||
// How much to inflate the seed lines to produce the first wave area.
|
||||
const float initial_step,
|
||||
// How much to inflate the first wave area and the successive wave areas in each step.
|
||||
const float other_step,
|
||||
// Number of inflate steps after the initial step.
|
||||
const size_t num_other_steps,
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation.
|
||||
const float max_inflation)
|
||||
{
|
||||
assert(! seed.empty() && seed.front().size() >= 2);
|
||||
Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents<true>(seed).inflated(max_inflation));
|
||||
ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping);
|
||||
// Now offset the remaining
|
||||
for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset)
|
||||
polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping);
|
||||
return to_polygons(polygons);
|
||||
}
|
||||
|
||||
// Resulting regions are sorted by boundary id and source id.
|
||||
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
std::vector<RegionExpansion> out;
|
||||
ClipperLib::Paths paths;
|
||||
ClipperLib::ClipperOffset co;
|
||||
co.ArcTolerance = params.arc_tolerance;
|
||||
co.ShortestEdgeLength = params.shortest_edge_length;
|
||||
for (auto it_seed = seeds.begin(); it_seed != seeds.end();) {
|
||||
auto it = it_seed;
|
||||
paths.clear();
|
||||
for (; it != seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it)
|
||||
paths.emplace_back(it->path);
|
||||
// Propagate the wavefront while clipping it with the trimmed boundary.
|
||||
// Collect the expanded polygons, merge them with the source polygons.
|
||||
RegionExpansion re;
|
||||
for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation))
|
||||
out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary });
|
||||
it_seed = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
return propagate_waves(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
|
||||
}
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps)
|
||||
{
|
||||
return propagate_waves(src, boundary, RegionExpansionParameters::build(expansion, expansion_step, max_nr_steps));
|
||||
}
|
||||
|
||||
// Returns regions per source ExPolygon expanded into boundary.
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
std::vector<RegionExpansion> expanded = propagate_waves(seeds, boundary, params);
|
||||
assert(std::is_sorted(seeds.begin(), seeds.end(), [](const auto &l, const auto &r){ return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); }));
|
||||
Polygons acc;
|
||||
std::vector<RegionExpansionEx> out;
|
||||
for (auto it = expanded.begin(); it != expanded.end(); ) {
|
||||
auto it2 = it;
|
||||
acc.clear();
|
||||
for (; it2 != expanded.end() && it2->boundary_id == it->boundary_id && it2->src_id == it->src_id; ++ it2)
|
||||
acc.emplace_back(std::move(it2->polygon));
|
||||
size_t size = it2 - it;
|
||||
if (size == 1)
|
||||
out.push_back({ ExPolygon{std::move(acc.front())}, it->src_id, it->boundary_id });
|
||||
else {
|
||||
ExPolygons expolys = union_ex(acc);
|
||||
reserve_more_power_of_2(out, expolys.size());
|
||||
for (ExPolygon &ex : expolys)
|
||||
out.push_back({ std::move(ex), it->src_id, it->boundary_id });
|
||||
}
|
||||
it = it2;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns regions per source ExPolygon expanded into boundary.
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(
|
||||
// Source regions that are supposed to touch the boundary.
|
||||
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
|
||||
const ExPolygons &src,
|
||||
const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float full_expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_expansion_steps)
|
||||
{
|
||||
auto params = RegionExpansionParameters::build(full_expansion, expansion_step, max_nr_expansion_steps);
|
||||
return propagate_waves_ex(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
|
||||
}
|
||||
|
||||
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps)
|
||||
{
|
||||
std::vector<Polygons> out(src.size(), Polygons{});
|
||||
for (RegionExpansion &r : propagate_waves(src, boundary, expansion, expansion_step, max_nr_steps))
|
||||
out[r.src_id].emplace_back(std::move(r.polygon));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ExPolygon> merge_expansions_into_expolygons(ExPolygons &&src, std::vector<RegionExpansion> &&expanded)
|
||||
{
|
||||
// expanded regions will be merged into source regions, thus they will be re-sorted by source id.
|
||||
std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; });
|
||||
uint32_t last = 0;
|
||||
Polygons acc;
|
||||
ExPolygons out;
|
||||
out.reserve(src.size());
|
||||
for (auto it = expanded.begin(); it != expanded.end();) {
|
||||
for (; last < it->src_id; ++ last)
|
||||
out.emplace_back(std::move(src[last]));
|
||||
acc.clear();
|
||||
assert(it->src_id == last);
|
||||
for (; it != expanded.end() && it->src_id == last; ++ it)
|
||||
acc.emplace_back(std::move(it->polygon));
|
||||
//FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon
|
||||
ExPolygon &src_ex = src[last ++];
|
||||
assert(! src_ex.contour.empty());
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
BoundingBox bbox = get_extents(acc);
|
||||
bbox.merge(get_extents(src_ex));
|
||||
SVG svg(debug_out_path("expand_merge_expolygons-failed-union=%d.svg", iRun ++).c_str(), bbox);
|
||||
svg.draw(acc);
|
||||
svg.draw_outline(acc, "black", scale_(0.05));
|
||||
svg.draw(src_ex, "red");
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
Point sample = src_ex.contour.front();
|
||||
append(acc, to_polygons(std::move(src_ex)));
|
||||
ExPolygons merged = union_safety_offset_ex(acc);
|
||||
// Expanding one expolygon by waves should not change connectivity of the source expolygon:
|
||||
// Single expolygon should be produced possibly with increased number of holes.
|
||||
if (merged.size() > 1) {
|
||||
// assert(merged.size() == 1);
|
||||
// There is something wrong with the initial waves. Most likely the bridge was not valid at all
|
||||
// or the boundary region was very close to some bridge edge, but not really touching.
|
||||
// Pick only a single merged expolygon, which contains one sample point of the source expolygon.
|
||||
auto aabb_tree = build_aabb_tree_over_expolygons(merged);
|
||||
int id = sample_in_expolygons(aabb_tree, merged, sample);
|
||||
assert(id != -1);
|
||||
if (id != -1)
|
||||
out.emplace_back(std::move(merged[id]));
|
||||
} else if (merged.size() == 1)
|
||||
out.emplace_back(std::move(merged.front()));
|
||||
}
|
||||
for (; last < uint32_t(src.size()); ++ last)
|
||||
out.emplace_back(std::move(src[last]));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
// expanded regions are sorted by boundary id and source id
|
||||
std::vector<RegionExpansion> expanded = propagate_waves(src, boundary, params);
|
||||
return merge_expansions_into_expolygons(std::move(src), std::move(expanded));
|
||||
}
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
||||
@@ -0,0 +1,118 @@
|
||||
#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
|
||||
#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
struct RegionExpansionParameters
|
||||
{
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
float tiny_expansion;
|
||||
// How much to inflate the seed lines to produce the first wave area.
|
||||
float initial_step;
|
||||
// How much to inflate the first wave area and the successive wave areas in each step.
|
||||
float other_step;
|
||||
// Number of inflate steps after the initial step.
|
||||
size_t num_other_steps;
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation.
|
||||
float max_inflation;
|
||||
|
||||
// Accuracy of the offsetter for wave propagation.
|
||||
double arc_tolerance;
|
||||
double shortest_edge_length;
|
||||
|
||||
static RegionExpansionParameters build(
|
||||
// Scaled expansion value
|
||||
float full_expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_expansion_steps);
|
||||
};
|
||||
|
||||
struct WaveSeed {
|
||||
uint32_t src;
|
||||
uint32_t boundary;
|
||||
Points path;
|
||||
};
|
||||
using WaveSeeds = std::vector<WaveSeed>;
|
||||
|
||||
inline bool lower_by_boundary_and_src(const WaveSeed &l, const WaveSeed &r)
|
||||
{
|
||||
return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src);
|
||||
}
|
||||
|
||||
inline bool lower_by_src_and_boundary(const WaveSeed &l, const WaveSeed &r)
|
||||
{
|
||||
return l.src < r.src || (l.src == r.src && l.boundary < r.boundary);
|
||||
}
|
||||
|
||||
// Expand src slightly outwards to intersect boundaries, trim the offsetted src polylines by the boundaries.
|
||||
// Return the trimmed paths annotated with their origin (source of the path, index of the boundary region).
|
||||
WaveSeeds wave_seeds(
|
||||
// Source regions that are supposed to touch the boundary.
|
||||
const ExPolygons &src,
|
||||
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
|
||||
const ExPolygons &boundary,
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
float tiny_expansion,
|
||||
bool sorted);
|
||||
|
||||
struct RegionExpansion
|
||||
{
|
||||
Polygon polygon;
|
||||
uint32_t src_id;
|
||||
uint32_t boundary_id;
|
||||
};
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps);
|
||||
|
||||
struct RegionExpansionEx
|
||||
{
|
||||
ExPolygon expolygon;
|
||||
uint32_t src_id;
|
||||
uint32_t boundary_id;
|
||||
};
|
||||
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps);
|
||||
|
||||
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps);
|
||||
|
||||
// Merge src with expansions, return the merged expolygons.
|
||||
std::vector<ExPolygon> merge_expansions_into_expolygons(ExPolygons &&src, std::vector<RegionExpansion> &&expanded);
|
||||
|
||||
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */
|
||||
@@ -0,0 +1,130 @@
|
||||
#ifndef ANYPTR_HPP
|
||||
#define ANYPTR_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// A general purpose pointer holder that can hold any type of smart pointer
|
||||
// or raw pointer which can own or not own any object they point to.
|
||||
// In case a raw pointer is stored, it is not destructed so ownership is
|
||||
// assumed to be foreign.
|
||||
//
|
||||
// The stored pointer is not checked for being null when dereferenced.
|
||||
//
|
||||
// This is a movable only object due to the fact that it can possibly hold
|
||||
// a unique_ptr which a non-copy.
|
||||
template<class T>
|
||||
class AnyPtr {
|
||||
enum { RawPtr, UPtr, ShPtr, WkPtr };
|
||||
|
||||
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
|
||||
|
||||
template<class Self> static T *get_ptr(Self &&s)
|
||||
{
|
||||
switch (s.ptr.which()) {
|
||||
case RawPtr: return boost::get<T *>(s.ptr);
|
||||
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
|
||||
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
|
||||
return shptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(TT *p = nullptr) : ptr{p}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
|
||||
{}
|
||||
|
||||
~AnyPtr() = default;
|
||||
|
||||
AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {}
|
||||
AnyPtr(const AnyPtr &other) = delete;
|
||||
|
||||
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
|
||||
AnyPtr &operator=(const AnyPtr &other) = delete;
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
const T &operator*() const { return *get_ptr(*this); }
|
||||
T &operator*() { return *get_ptr(*this); }
|
||||
|
||||
T *operator->() { return get_ptr(*this); }
|
||||
const T *operator->() const { return get_ptr(*this); }
|
||||
|
||||
T *get() { return get_ptr(*this); }
|
||||
const T *get() const { return get_ptr(*this); }
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
switch (ptr.which()) {
|
||||
case RawPtr: return bool(boost::get<T *>(ptr));
|
||||
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
|
||||
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
|
||||
return bool(shptr);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the stored pointer is a shared or weak pointer, returns a reference
|
||||
// counted copy. Empty shared pointer is returned otherwise.
|
||||
std::shared_ptr<T> get_shared_cpy() const
|
||||
{
|
||||
std::shared_ptr<T> ret;
|
||||
|
||||
switch (ptr.which()) {
|
||||
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
|
||||
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If the underlying pointer is unique, convert to shared pointer
|
||||
void convert_unique_to_shared()
|
||||
{
|
||||
if (ptr.which() == UPtr)
|
||||
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
|
||||
}
|
||||
|
||||
// Returns true if the data is owned by this AnyPtr instance
|
||||
bool is_owned() const noexcept
|
||||
{
|
||||
return ptr.which() == UPtr || ptr.which() == ShPtr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ANYPTR_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,423 @@
|
||||
#ifndef slic3r_AppConfig_hpp_
|
||||
#define slic3r_AppConfig_hpp_
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <boost/algorithm/string/trim_all.hpp>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Semver.hpp"
|
||||
#include "calib.hpp"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
#define ENV_DEV_HOST "0"
|
||||
#define ENV_QAT_HOST "1"
|
||||
#define ENV_PRE_HOST "2"
|
||||
#define ENV_PRODUCT_HOST "3"
|
||||
|
||||
#define SETTING_PROJECT_LOAD_BEHAVIOUR "project_load_behaviour"
|
||||
#define OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_ALL "load_all"
|
||||
#define OPTION_PROJECT_LOAD_BEHAVIOUR_ASK_WHEN_RELEVANT "ask_when_relevant"
|
||||
#define OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK "always_ask"
|
||||
#define OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY "load_geometry_only"
|
||||
|
||||
#define SETTING_NETWORK_PLUGIN_VERSION "network_plugin_version"
|
||||
#define SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS "network_plugin_skipped_versions"
|
||||
#define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled"
|
||||
#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later"
|
||||
#define SETTING_USE_ENCRYPTED_TOKEN_FILE "use_encrypted_token_file"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
||||
#else
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01"
|
||||
#endif
|
||||
|
||||
#define SUPPORT_DARK_MODE
|
||||
//#define _MSW_DARK_MODE
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
// Connected LAN mode BambuLab printer
|
||||
struct BBLocalMachine
|
||||
{
|
||||
std::string dev_name;
|
||||
std::string dev_ip;
|
||||
std::string dev_id; /* serial number */
|
||||
std::string printer_type; /* model_id */
|
||||
|
||||
bool operator==(const BBLocalMachine& other) const
|
||||
{
|
||||
return dev_name == other.dev_name && dev_ip == other.dev_ip && dev_id == other.dev_id && printer_type == other.printer_type;
|
||||
}
|
||||
bool operator!=(const BBLocalMachine& other) const { return !operator==(other); }
|
||||
};
|
||||
|
||||
class AppConfig
|
||||
{
|
||||
public:
|
||||
enum class EAppMode : unsigned char
|
||||
{
|
||||
Editor,
|
||||
GCodeViewer
|
||||
};
|
||||
|
||||
//BBS: remove GCodeViewer as seperate APP logic
|
||||
explicit AppConfig() :
|
||||
m_dirty(false),
|
||||
m_orig_version(Semver::invalid()),
|
||||
m_mode(EAppMode::Editor),
|
||||
m_legacy_datadir(false)
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
|
||||
std::string get_language_code();
|
||||
std::string get_hms_host();
|
||||
bool get_stealth_mode();
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
// Override missing or keys with their defaults.
|
||||
void set_defaults();
|
||||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
// return error string or empty strinf
|
||||
std::string load();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
// Does this config need to be saved?
|
||||
bool dirty() const { return m_dirty; }
|
||||
|
||||
|
||||
void set_dirty() { m_dirty = true; }
|
||||
|
||||
// Const accessor, it will return false if a section or a key does not exist.
|
||||
bool get(const std::string §ion, const std::string &key, std::string &value) const
|
||||
{
|
||||
value.clear();
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
if (it2 == it->second.end())
|
||||
return false;
|
||||
value = it2->second;
|
||||
return true;
|
||||
}
|
||||
std::string get(const std::string §ion, const std::string &key) const
|
||||
{ std::string value; this->get(section, key, value); return value; }
|
||||
std::string get(const std::string &key) const
|
||||
{ std::string value; this->get("app", key, value); return value; }
|
||||
bool get_bool(const std::string §ion, const std::string &key) const
|
||||
{ return this->get(section, key) == "true" || this->get(key) == "1"; }
|
||||
bool get_bool(const std::string &key) const
|
||||
{ return this->get_bool("app", key); }
|
||||
void set(const std::string §ion, const std::string &key, const std::string &value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(! key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string &old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_str(const std::string& section, const std::string& key, const std::string& value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(!key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string& old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set(const std::string& section, const std::string &key, bool value)
|
||||
{
|
||||
if (value){
|
||||
set(section, key, std::string("true"));
|
||||
} else {
|
||||
set(section, key, std::string("false"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void set(const std::string &key, const std::string &value)
|
||||
{ this->set("app", key, value); }
|
||||
|
||||
void set_bool(const std::string &key, const bool &value)
|
||||
{
|
||||
this->set("app", key, value);
|
||||
}
|
||||
|
||||
bool has(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
return it2 != it->second.end() && ! it2->second.empty();
|
||||
}
|
||||
bool has(const std::string &key) const
|
||||
{ return this->has("app", key); }
|
||||
|
||||
void erase(const std::string §ion, const std::string &key)
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it != m_storage.end()) {
|
||||
it->second.erase(key);
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_section(const std::string §ion) const
|
||||
{ return m_storage.find(section) != m_storage.end(); }
|
||||
const std::map<std::string, std::string>& get_section(const std::string §ion) const
|
||||
{ return m_storage.find(section)->second; }
|
||||
void set_section(const std::string §ion, const std::map<std::string, std::string>& data)
|
||||
{ m_storage[section] = data; }
|
||||
void clear_section(const std::string §ion)
|
||||
{ m_storage[section].clear(); }
|
||||
|
||||
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
|
||||
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
|
||||
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
|
||||
void set_vendors(const AppConfig &from);
|
||||
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
|
||||
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
|
||||
const VendorMap& vendors() const { return m_vendors; }
|
||||
|
||||
// Orca printer settings
|
||||
typedef std::map<std::string, nlohmann::json> MachineSettingMap;
|
||||
bool has_printer_settings(std::string printer) const {
|
||||
return m_printer_settings.find(printer) != m_printer_settings.end();
|
||||
}
|
||||
void clear_printer_settings(std::string printer) {
|
||||
m_printer_settings.erase(printer);
|
||||
m_dirty = true;
|
||||
}
|
||||
bool has_printer_setting(std::string printer, std::string name) {
|
||||
if (!has_printer_settings(printer))
|
||||
return false;
|
||||
if (!m_printer_settings[printer].contains(name))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
std::string get_printer_setting(std::string printer, std::string name) {
|
||||
if (!has_printer_setting(printer, name))
|
||||
return "";
|
||||
return m_printer_settings[printer][name];
|
||||
}
|
||||
void set_printer_setting(std::string printer, std::string name, std::string value) {
|
||||
m_printer_settings[printer][name] = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
const std::map<std::string, BBLocalMachine>& get_local_machines() const { return m_local_machines; }
|
||||
void erase_local_machine(std::string dev_id)
|
||||
{
|
||||
auto it = m_local_machines.find(dev_id);
|
||||
if (it != m_local_machines.end()) {
|
||||
m_local_machines.erase(it);
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
void update_local_machine(const BBLocalMachine& machine)
|
||||
{
|
||||
auto it = m_local_machines.find(machine.dev_id);
|
||||
if (it != m_local_machines.end()) {
|
||||
const auto& current = it->second;
|
||||
if (machine != current) {
|
||||
m_local_machines[machine.dev_id] = machine;
|
||||
m_dirty = true;
|
||||
}
|
||||
} else {
|
||||
m_local_machines[machine.dev_id] = machine;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_filament_presets() const { return m_filament_presets; }
|
||||
void set_filament_presets(const std::vector<std::string> &filament_presets){
|
||||
m_filament_presets = filament_presets;
|
||||
m_dirty = true;
|
||||
}
|
||||
const std::vector<std::string> &get_filament_colors() const { return m_filament_colors; }
|
||||
void set_filament_colors(const std::vector<std::string> &filament_colors){
|
||||
m_filament_colors = filament_colors;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
const std::vector<PrinterCaliInfo> &get_printer_cali_infos() const { return m_printer_cali_infos; }
|
||||
void save_printer_cali_infos(const PrinterCaliInfo& cali_info, bool need_change_status = true);
|
||||
|
||||
// return recent/last_opened_folder or recent/settings_folder or empty string.
|
||||
std::string get_last_dir() const;
|
||||
void update_config_dir(const std::string &dir);
|
||||
void update_skein_dir(const std::string &dir);
|
||||
|
||||
//std::string get_last_output_dir(const std::string &alt) const;
|
||||
//void update_last_output_dir(const std::string &dir);
|
||||
std::string get_last_output_dir(const std::string& alt, const bool removable = false) const;
|
||||
void update_last_output_dir(const std::string &dir, const bool removable = false);
|
||||
|
||||
// BBS: backup & restore
|
||||
std::string get_last_backup_dir() const;
|
||||
void update_last_backup_dir(const std::string &dir);
|
||||
|
||||
std::string get_region();
|
||||
std::string get_country_code();
|
||||
bool is_engineering_region();
|
||||
|
||||
void save_custom_color_to_config(const std::vector<std::string> &colors);
|
||||
std::vector<std::string> get_custom_color_from_config();
|
||||
|
||||
void save_nozzle_volume_types_to_config(const std::string& printer_name, const std::string& nozzle_volume_types);
|
||||
std::string get_nozzle_volume_types_from_config(const std::string& printer_name);
|
||||
|
||||
// reset the current print / filament / printer selections, so that
|
||||
// the PresetBundle::load_selections(const AppConfig &config) call will select
|
||||
// the first non-default preset when called.
|
||||
void reset_selections();
|
||||
|
||||
// Get the default config path from Slic3r::data_dir().
|
||||
std::string config_path();
|
||||
|
||||
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
|
||||
|
||||
// Get the Slic3r version check url.
|
||||
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
|
||||
std::string version_check_url() const;
|
||||
|
||||
// Get the Orca profile update url.
|
||||
std::string profile_update_url() const;
|
||||
|
||||
// Returns the original Slic3r version found in the ini file before it was overwritten
|
||||
// by the current version
|
||||
Semver orig_version() const { return m_orig_version; }
|
||||
|
||||
// Does the config file exist?
|
||||
bool exists();
|
||||
|
||||
void set_loading_path(const std::string& path) { m_loading_path = path; }
|
||||
std::string loading_path() { return (m_loading_path.empty() ? config_path() : m_loading_path); }
|
||||
|
||||
std::vector<std::string> get_recent_projects() const;
|
||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||
|
||||
void set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone, double zoom_speed, bool swap_yz, bool invert_x, bool invert_y, bool invert_z, bool invert_yaw, bool invert_pitch, bool invert_roll);
|
||||
std::vector<std::string> get_mouse_device_names() const;
|
||||
bool get_mouse_device_translation_speed(const std::string& name, double& speed) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "translation_speed", speed); }
|
||||
bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "translation_deadzone", deadzone); }
|
||||
bool get_mouse_device_rotation_speed(const std::string& name, float& speed) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "rotation_speed", speed); }
|
||||
bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "rotation_deadzone", deadzone); }
|
||||
bool get_mouse_device_zoom_speed(const std::string& name, double& speed) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "zoom_speed", speed); }
|
||||
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
|
||||
bool get_mouse_device_invert_x(const std::string& name, bool& invert) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "invert_x", invert); }
|
||||
bool get_mouse_device_invert_y(const std::string& name, bool& invert) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "invert_y", invert); }
|
||||
bool get_mouse_device_invert_z(const std::string& name, bool& invert) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "invert_z", invert); }
|
||||
bool get_mouse_device_invert_yaw(const std::string& name, bool& invert) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "invert_yaw", invert); }
|
||||
bool get_mouse_device_invert_pitch(const std::string& name, bool& invert) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "invert_pitch", invert); }
|
||||
bool get_mouse_device_invert_roll(const std::string& name, bool& invert) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "invert_roll", invert); }
|
||||
|
||||
static const std::string SECTION_FILAMENTS;
|
||||
static const std::string SECTION_MATERIALS;
|
||||
static const std::string SECTION_EMBOSS_STYLE;
|
||||
|
||||
std::string get_network_plugin_version() const;
|
||||
void set_network_plugin_version(const std::string& version);
|
||||
|
||||
std::vector<std::string> get_skipped_network_versions() const;
|
||||
void add_skipped_network_version(const std::string& version);
|
||||
bool is_network_version_skipped(const std::string& version) const;
|
||||
void clear_skipped_network_versions();
|
||||
|
||||
bool is_network_update_prompt_disabled() const;
|
||||
void set_network_update_prompt_disabled(bool disabled);
|
||||
|
||||
bool should_remind_network_update_later() const;
|
||||
void set_remind_network_update_later(bool remind);
|
||||
void clear_remind_network_update_later();
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + device_name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it_val = it->second.find(parameter_name);
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
out = T(string_to_double_decimal_point(it_val->second));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Type of application: Editor or GCodeViewer
|
||||
EAppMode m_mode { EAppMode::Editor };
|
||||
// Map of section, name -> value
|
||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||
|
||||
// Map of enabled vendors / models / variants
|
||||
VendorMap m_vendors;
|
||||
|
||||
// Preset for each machine
|
||||
MachineSettingMap m_printer_settings;
|
||||
// Has any value been modified since the config.ini has been last saved or loaded?
|
||||
bool m_dirty;
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
|
||||
std::string m_loading_path;
|
||||
|
||||
std::vector<std::string> m_filament_presets;
|
||||
std::vector<std::string> m_filament_colors;
|
||||
std::vector<std::string> m_filament_multi_colors;
|
||||
std::vector<std::string> m_filament_color_types;
|
||||
|
||||
std::vector<PrinterCaliInfo> m_printer_cali_infos;
|
||||
|
||||
std::map<std::string, BBLocalMachine> m_local_machines;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AppConfig_hpp_ */
|
||||
@@ -0,0 +1,77 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
BeadingStrategy::BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle)
|
||||
: optimal_width(optimal_width)
|
||||
, wall_split_middle_threshold(wall_split_middle_threshold)
|
||||
, wall_add_middle_threshold(wall_add_middle_threshold)
|
||||
, default_transition_length(default_transition_length)
|
||||
, transitioning_angle(transitioning_angle)
|
||||
{
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
BeadingStrategy::BeadingStrategy(const BeadingStrategy &other)
|
||||
: optimal_width(other.optimal_width)
|
||||
, wall_split_middle_threshold(other.wall_split_middle_threshold)
|
||||
, wall_add_middle_threshold(other.wall_add_middle_threshold)
|
||||
, default_transition_length(other.default_transition_length)
|
||||
, transitioning_angle(other.transitioning_angle)
|
||||
, name(other.name)
|
||||
{}
|
||||
|
||||
coord_t BeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count == 0)
|
||||
return scaled<coord_t>(0.01);
|
||||
return default_transition_length;
|
||||
}
|
||||
|
||||
float BeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
coord_t lower_optimum = getOptimalThickness(lower_bead_count);
|
||||
coord_t transition_point = getTransitionThickness(lower_bead_count);
|
||||
coord_t upper_optimum = getOptimalThickness(lower_bead_count + 1);
|
||||
return 1.0 - float(transition_point - lower_optimum) / float(upper_optimum - lower_optimum);
|
||||
}
|
||||
|
||||
std::vector<coord_t> BeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string BeadingStrategy::toString() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
double BeadingStrategy::getSplitMiddleThreshold() const
|
||||
{
|
||||
return wall_split_middle_threshold;
|
||||
}
|
||||
|
||||
double BeadingStrategy::getTransitioningAngle() const
|
||||
{
|
||||
return transitioning_angle;
|
||||
}
|
||||
|
||||
coord_t BeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return optimal_width * bead_count;
|
||||
}
|
||||
|
||||
coord_t BeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
const coord_t lower_ideal_width = getOptimalThickness(lower_bead_count);
|
||||
const coord_t higher_ideal_width = getOptimalThickness(lower_bead_count + 1);
|
||||
const double threshold = lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold;
|
||||
return lower_ideal_width + threshold * (higher_ideal_width - lower_ideal_width);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef BEADING_STRATEGY_H
|
||||
#define BEADING_STRATEGY_H
|
||||
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename T> constexpr T pi_div(const T div) { return static_cast<T>(M_PI) / div; }
|
||||
|
||||
/*!
|
||||
* Mostly virtual base class template.
|
||||
*
|
||||
* Strategy for covering a given (constant) horizontal model thickness with a number of beads.
|
||||
*
|
||||
* The beads may have different widths.
|
||||
*
|
||||
* TODO: extend with printing order?
|
||||
*/
|
||||
class BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The beading for a given horizontal model thickness.
|
||||
*/
|
||||
struct Beading
|
||||
{
|
||||
coord_t total_thickness;
|
||||
std::vector<coord_t> bead_widths; //! The line width of each bead from the outer inset inward
|
||||
std::vector<coord_t> toolpath_locations; //! The distance of the toolpath location of each bead from the outline
|
||||
coord_t left_over; //! The distance not covered by any bead; gap area.
|
||||
};
|
||||
|
||||
BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle = pi_div(3));
|
||||
|
||||
BeadingStrategy(const BeadingStrategy &other);
|
||||
|
||||
virtual ~BeadingStrategy() = default;
|
||||
|
||||
/*!
|
||||
* Retrieve the bead widths with which to cover a given thickness.
|
||||
*
|
||||
* Requirement: Given a constant \p bead_count the output of each bead width must change gradually along with the \p thickness.
|
||||
*
|
||||
* \note The \p bead_count might be different from the \ref BeadingStrategy::optimal_bead_count
|
||||
*/
|
||||
virtual Beading compute(coord_t thickness, coord_t bead_count) const = 0;
|
||||
|
||||
/*!
|
||||
* The ideal thickness for a given \param bead_count
|
||||
*/
|
||||
virtual coord_t getOptimalThickness(coord_t bead_count) const;
|
||||
|
||||
/*!
|
||||
* The model thickness at which \ref BeadingStrategy::optimal_bead_count transitions from \p lower_bead_count to \p lower_bead_count + 1
|
||||
*/
|
||||
virtual coord_t getTransitionThickness(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* The number of beads should we ideally usefor a given model thickness
|
||||
*/
|
||||
virtual coord_t getOptimalBeadCount(coord_t thickness) const = 0;
|
||||
|
||||
/*!
|
||||
* The length of the transitioning region along the marked / significant regions of the skeleton.
|
||||
*
|
||||
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps with some incline defined by their length.
|
||||
*/
|
||||
virtual coord_t getTransitioningLength(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* The fraction of the transition length to put between the lower end of the transition and the point where the unsmoothed bead count jumps.
|
||||
*
|
||||
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps which could be positioned relative to the jump location.
|
||||
*/
|
||||
virtual float getTransitionAnchorPos(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* Get the locations in a bead count region where \ref BeadingStrategy::compute exhibits a bend in the widths.
|
||||
* Ordered from lower thickness to higher.
|
||||
*
|
||||
* This is used to insert extra support bones into the skeleton, so that the resulting beads in long trapezoids don't linearly change between the two ends.
|
||||
*/
|
||||
virtual std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const;
|
||||
|
||||
virtual std::string toString() const;
|
||||
|
||||
double getSplitMiddleThreshold() const;
|
||||
double getTransitioningAngle() const;
|
||||
|
||||
protected:
|
||||
std::string name;
|
||||
|
||||
coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances.
|
||||
|
||||
double wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width.
|
||||
|
||||
double wall_add_middle_threshold; //! Threshold when a new middle wall should be added between an even number of walls, as a ratio of the optimal wall width.
|
||||
|
||||
coord_t default_transition_length; //! The length of the region to smoothly transfer between bead counts
|
||||
|
||||
/*!
|
||||
* The maximum angle between outline segments smaller than which we are going to add transitions
|
||||
* Equals 180 - the "limit bisector angle" from the paper
|
||||
*/
|
||||
double transitioning_angle;
|
||||
};
|
||||
|
||||
using BeadingStrategyPtr = std::unique_ptr<BeadingStrategy>;
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,58 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "BeadingStrategyFactory.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "LimitedBeadingStrategy.hpp"
|
||||
#include "WideningBeadingStrategy.hpp"
|
||||
#include "DistributedBeadingStrategy.hpp"
|
||||
#include "RedistributeBeadingStrategy.hpp"
|
||||
#include "OuterWallInsetBeadingStrategy.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
BeadingStrategyPtr BeadingStrategyFactory::makeStrategy(const coord_t preferred_bead_width_outer,
|
||||
const coord_t preferred_bead_width_inner,
|
||||
const coord_t preferred_transition_length,
|
||||
const float transitioning_angle,
|
||||
const bool print_thin_walls,
|
||||
const coord_t min_bead_width,
|
||||
const coord_t min_feature_size,
|
||||
const double wall_split_middle_threshold,
|
||||
const double wall_add_middle_threshold,
|
||||
const coord_t max_bead_count,
|
||||
const coord_t outer_wall_offset,
|
||||
const int inward_distributed_center_wall_count,
|
||||
const double minimum_variable_line_ratio)
|
||||
{
|
||||
// Handle a special case when there is just one external perimeter.
|
||||
// Because big differences in bead width for inner and other perimeters cause issues with current beading strategies.
|
||||
const coord_t optimal_width = max_bead_count <= 2 ? preferred_bead_width_outer : preferred_bead_width_inner;
|
||||
BeadingStrategyPtr ret = std::make_unique<DistributedBeadingStrategy>(optimal_width, preferred_transition_length, transitioning_angle,
|
||||
wall_split_middle_threshold, wall_add_middle_threshold,
|
||||
inward_distributed_center_wall_count);
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << ".";
|
||||
ret = std::make_unique<RedistributeBeadingStrategy>(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret));
|
||||
|
||||
if (print_thin_walls) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << ".";
|
||||
ret = std::make_unique<WideningBeadingStrategy>(std::move(ret), min_feature_size, min_bead_width);
|
||||
}
|
||||
// Orca: we allow negative outer_wall_offset here
|
||||
if (outer_wall_offset != 0) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << ".";
|
||||
ret = std::make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, std::move(ret));
|
||||
}
|
||||
|
||||
// Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch.
|
||||
BOOST_LOG_TRIVIAL(trace) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << ".";
|
||||
ret = std::make_unique<LimitedBeadingStrategy>(max_bead_count, std::move(ret));
|
||||
return ret;
|
||||
}
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef BEADING_STRATEGY_FACTORY_H
|
||||
#define BEADING_STRATEGY_FACTORY_H
|
||||
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "../../Point.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class BeadingStrategyFactory
|
||||
{
|
||||
public:
|
||||
static BeadingStrategyPtr makeStrategy
|
||||
(
|
||||
coord_t preferred_bead_width_outer = scaled<coord_t>(0.0005),
|
||||
coord_t preferred_bead_width_inner = scaled<coord_t>(0.0005),
|
||||
coord_t preferred_transition_length = scaled<coord_t>(0.0004),
|
||||
float transitioning_angle = M_PI / 4.0,
|
||||
bool print_thin_walls = false,
|
||||
coord_t min_bead_width = 0,
|
||||
coord_t min_feature_size = 0,
|
||||
double wall_split_middle_threshold = 0.5,
|
||||
double wall_add_middle_threshold = 0.5,
|
||||
coord_t max_bead_count = 0,
|
||||
coord_t outer_wall_offset = 0,
|
||||
int inward_distributed_center_wall_count = 2,
|
||||
double minimum_variable_line_width = 0.5
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // BEADING_STRATEGY_FACTORY_H
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
#include <numeric>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "DistributedBeadingStrategy.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width,
|
||||
const coord_t default_transition_length,
|
||||
const double transitioning_angle,
|
||||
const double wall_split_middle_threshold,
|
||||
const double wall_add_middle_threshold,
|
||||
const int distribution_radius)
|
||||
: BeadingStrategy(optimal_width, wall_split_middle_threshold, wall_add_middle_threshold, default_transition_length, transitioning_angle)
|
||||
{
|
||||
if(distribution_radius >= 2)
|
||||
one_over_distribution_radius_squared = 1.0f / (distribution_radius - 1) * 1.0f / (distribution_radius - 1);
|
||||
else
|
||||
one_over_distribution_radius_squared = 1.0f / 1 * 1.0f / 1;
|
||||
name = "DistributedBeadingStrategy";
|
||||
}
|
||||
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, const coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
ret.total_thickness = thickness;
|
||||
if (bead_count > 2) {
|
||||
const coord_t to_be_divided = thickness - bead_count * optimal_width;
|
||||
const float middle = static_cast<float>(bead_count - 1) / 2;
|
||||
|
||||
const auto getWeight = [middle, this](coord_t bead_idx) {
|
||||
const float dev_from_middle = bead_idx - middle;
|
||||
return std::max(0.0f, 1.0f - one_over_distribution_radius_squared * dev_from_middle * dev_from_middle);
|
||||
};
|
||||
|
||||
std::vector<float> weights;
|
||||
weights.resize(bead_count);
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++)
|
||||
weights[bead_idx] = getWeight(bead_idx);
|
||||
|
||||
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
|
||||
coord_t accumulated_width = 0;
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
|
||||
const float weight_fraction = weights[bead_idx] / total_weight;
|
||||
const coord_t splitup_left_over_weight = to_be_divided * weight_fraction;
|
||||
const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width : optimal_width + splitup_left_over_weight;
|
||||
|
||||
// Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations
|
||||
// could be off by 1 because of rounding errors.
|
||||
if (bead_idx == 0)
|
||||
ret.toolpath_locations.emplace_back(width / 2);
|
||||
else
|
||||
ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
|
||||
ret.bead_widths.emplace_back(width);
|
||||
accumulated_width += width;
|
||||
}
|
||||
ret.left_over = 0;
|
||||
assert((accumulated_width + ret.left_over) == thickness);
|
||||
} else if (bead_count == 2) {
|
||||
const coord_t outer_width = thickness / 2;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.toolpath_locations.emplace_back(outer_width / 2);
|
||||
ret.toolpath_locations.emplace_back(thickness - outer_width / 2);
|
||||
ret.left_over = 0;
|
||||
} else if (bead_count == 1) {
|
||||
const coord_t outer_width = thickness;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.toolpath_locations.emplace_back(outer_width / 2);
|
||||
ret.left_over = 0;
|
||||
} else {
|
||||
ret.left_over = thickness;
|
||||
}
|
||||
|
||||
assert(([&ret = std::as_const(ret), thickness]() -> bool {
|
||||
coord_t total_bead_width = 0;
|
||||
for (const coord_t &bead_width : ret.bead_widths)
|
||||
total_bead_width += bead_width;
|
||||
return (total_bead_width + ret.left_over) == thickness;
|
||||
}()));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t DistributedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure.
|
||||
const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines.
|
||||
const coord_t minimum_line_width = optimal_width * (naive_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold);
|
||||
return naive_count + (remainder >= minimum_line_width); // If there's enough space, fit an extra one.
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef DISTRIBUTED_BEADING_STRATEGY_H
|
||||
#define DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This beading strategy chooses a wall count that would make the line width
|
||||
* deviate the least from the optimal line width, and then distributes the lines
|
||||
* evenly among the thickness available.
|
||||
*/
|
||||
class DistributedBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
protected:
|
||||
float one_over_distribution_radius_squared; // (1 / distribution_radius)^2
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \param distribution_radius the radius (in number of beads) over which to distribute the discrepancy between the feature size and the optimal thickness
|
||||
*/
|
||||
DistributedBeadingStrategy(coord_t optimal_width,
|
||||
coord_t default_transition_length,
|
||||
double transitioning_angle,
|
||||
double wall_split_middle_threshold,
|
||||
double wall_add_middle_threshold,
|
||||
int distribution_radius);
|
||||
|
||||
~DistributedBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // DISTRIBUTED_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,129 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
|
||||
#include "LimitedBeadingStrategy.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
std::string LimitedBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("LimitedBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float LimitedBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
LimitedBeadingStrategy::LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent)
|
||||
, max_bead_count(max_bead_count)
|
||||
, parent(std::move(parent))
|
||||
{
|
||||
if (max_bead_count % 2 == 1)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "LimitedBeadingStrategy with odd bead count is odd indeed!";
|
||||
}
|
||||
}
|
||||
|
||||
LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (bead_count <= max_bead_count)
|
||||
{
|
||||
Beading ret = parent->compute(thickness, bead_count);
|
||||
bead_count = ret.toolpath_locations.size();
|
||||
|
||||
if (bead_count % 2 == 0 && bead_count == max_bead_count)
|
||||
{
|
||||
const coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
|
||||
const coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
assert(bead_count == max_bead_count + 1);
|
||||
if(bead_count != max_bead_count + 1)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Too many beads! " << bead_count << " != " << max_bead_count + 1;
|
||||
}
|
||||
|
||||
coord_t optimal_thickness = parent->getOptimalThickness(max_bead_count);
|
||||
Beading ret = parent->compute(optimal_thickness, max_bead_count);
|
||||
bead_count = ret.toolpath_locations.size();
|
||||
ret.left_over += thickness - ret.total_thickness;
|
||||
ret.total_thickness = thickness;
|
||||
|
||||
// Enforce symmetry
|
||||
if (bead_count % 2 == 1) {
|
||||
ret.toolpath_locations[bead_count / 2] = thickness / 2;
|
||||
ret.bead_widths[bead_count / 2] = thickness - optimal_thickness;
|
||||
}
|
||||
for (coord_t bead_idx = 0; bead_idx < (bead_count + 1) / 2; bead_idx++)
|
||||
ret.toolpath_locations[bead_count - 1 - bead_idx] = thickness - ret.toolpath_locations[bead_idx];
|
||||
|
||||
//Create a "fake" inner wall with 0 width to indicate the edge of the walled area.
|
||||
//This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls.
|
||||
coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
|
||||
coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
|
||||
|
||||
//Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then.
|
||||
const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1);
|
||||
innermost_toolpath_location = ret.toolpath_locations[opposite_bead];
|
||||
innermost_toolpath_width = ret.bead_widths[opposite_bead];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
if (bead_count <= max_bead_count)
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
assert(false);
|
||||
return scaled<coord_t>(1000.); // 1 meter (Cura was returning 10 meter)
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count < max_bead_count)
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
|
||||
if (lower_bead_count == max_bead_count)
|
||||
return parent->getOptimalThickness(lower_bead_count + 1) - scaled<coord_t>(0.01);
|
||||
|
||||
assert(false);
|
||||
return scaled<coord_t>(900.); // 0.9 meter;
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
coord_t parent_bead_count = parent->getOptimalBeadCount(thickness);
|
||||
if (parent_bead_count <= max_bead_count) {
|
||||
return parent->getOptimalBeadCount(thickness);
|
||||
} else if (parent_bead_count == max_bead_count + 1) {
|
||||
if (thickness < parent->getOptimalThickness(max_bead_count + 1) - scaled<coord_t>(0.01))
|
||||
return max_bead_count;
|
||||
else
|
||||
return max_bead_count + 1;
|
||||
}
|
||||
else return max_bead_count + 1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,52 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIMITED_BEADING_STRATEGY_H
|
||||
#define LIMITED_BEADING_STRATEGY_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This is a meta-strategy that can be applied on top of any other beading
|
||||
* strategy, which limits the thickness of the walls to the thickness that the
|
||||
* lines can reasonably print.
|
||||
*
|
||||
* The width of the wall is limited to the maximum number of contours times the
|
||||
* maximum width of each of these contours.
|
||||
*
|
||||
* If the width of the wall gets limited, this strategy outputs one additional
|
||||
* bead with 0 width. This bead is used to denote the limits of the walled area.
|
||||
* Other structures can then use this border to align their structures to, such
|
||||
* as to create correctly overlapping infill or skin, or to align the infill
|
||||
* pattern to any extra infill walls.
|
||||
*/
|
||||
class LimitedBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
LimitedBeadingStrategy(coord_t max_bead_count, BeadingStrategyPtr parent);
|
||||
|
||||
~LimitedBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
|
||||
protected:
|
||||
const coord_t max_bead_count;
|
||||
const BeadingStrategyPtr parent;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // LIMITED_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,62 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "OuterWallInsetBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent), parent(std::move(parent)), outer_wall_offset(outer_wall_offset)
|
||||
{
|
||||
name = "OuterWallOfsetBeadingStrategy";
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
return parent->getOptimalBeadCount(thickness);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
std::string OuterWallInsetBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("OuterWallOfsetBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
BeadingStrategy::Beading OuterWallInsetBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret = parent->compute(thickness, bead_count);
|
||||
|
||||
// Actual count and thickness as represented by extant walls. Don't count any potential zero-width 'signaling' walls.
|
||||
bead_count = std::count_if(ret.bead_widths.begin(), ret.bead_widths.end(), [](const coord_t width) { return width > 0; });
|
||||
|
||||
// No need to apply any inset if there is just a single wall.
|
||||
if (bead_count < 2)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Actually move the outer wall inside. Ensure that the outer wall never goes beyond the middle line.
|
||||
ret.toolpath_locations[0] = std::min(ret.toolpath_locations[0] + outer_wall_offset, thickness / 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,38 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
#define OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
/*
|
||||
* This is a meta strategy that allows for the outer wall to be inset towards the inside of the model.
|
||||
*/
|
||||
class OuterWallInsetBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent);
|
||||
|
||||
~OuterWallInsetBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
|
||||
private:
|
||||
BeadingStrategyPtr parent;
|
||||
coord_t outer_wall_offset;
|
||||
};
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,100 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "RedistributeBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
RedistributeBeadingStrategy::RedistributeBeadingStrategy(const coord_t optimal_width_outer,
|
||||
const double minimum_variable_line_ratio,
|
||||
BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent)
|
||||
, parent(std::move(parent))
|
||||
, optimal_width_outer(optimal_width_outer)
|
||||
, minimum_variable_line_ratio(minimum_variable_line_ratio)
|
||||
{
|
||||
name = "RedistributeBeadingStrategy";
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
const coord_t inner_bead_count = std::max(static_cast<coord_t>(0), bead_count - 2);
|
||||
const coord_t outer_bead_count = bead_count - inner_bead_count;
|
||||
return parent->getOptimalThickness(inner_bead_count) + optimal_width_outer * outer_bead_count;
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
switch (lower_bead_count) {
|
||||
case 0: return minimum_variable_line_ratio * optimal_width_outer;
|
||||
case 1: return (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer;
|
||||
default: return parent->getTransitionThickness(lower_bead_count - 2) + 2 * optimal_width_outer;
|
||||
}
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
if (thickness < minimum_variable_line_ratio * optimal_width_outer)
|
||||
return 0;
|
||||
if (thickness <= 2 * optimal_width_outer)
|
||||
return thickness > (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer ? 2 : 1;
|
||||
return parent->getOptimalBeadCount(thickness - 2 * optimal_width_outer) + 2;
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float RedistributeBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
std::string RedistributeBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("RedistributeBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
BeadingStrategy::Beading RedistributeBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
// Take care of all situations in which no lines are actually produced:
|
||||
if (bead_count == 0 || thickness < minimum_variable_line_ratio * optimal_width_outer) {
|
||||
ret.left_over = thickness;
|
||||
ret.total_thickness = thickness;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Compute the beadings of the inner walls, if any:
|
||||
const coord_t inner_bead_count = bead_count - 2;
|
||||
const coord_t inner_thickness = thickness - 2 * optimal_width_outer;
|
||||
if (inner_bead_count > 0 && inner_thickness > 0) {
|
||||
ret = parent->compute(inner_thickness, inner_bead_count);
|
||||
for (auto &toolpath_location : ret.toolpath_locations) toolpath_location += optimal_width_outer;
|
||||
}
|
||||
|
||||
// Insert the outer wall(s) around the previously computed inner wall(s), which may be empty:
|
||||
const coord_t actual_outer_thickness = bead_count > 2 ? std::min(thickness / 2, optimal_width_outer) : thickness / bead_count;
|
||||
ret.bead_widths.insert(ret.bead_widths.begin(), actual_outer_thickness);
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin(), actual_outer_thickness / 2);
|
||||
if (bead_count > 1) {
|
||||
ret.bead_widths.push_back(actual_outer_thickness);
|
||||
ret.toolpath_locations.push_back(thickness - actual_outer_thickness / 2);
|
||||
}
|
||||
|
||||
// Ensure correct total and left over thickness.
|
||||
ret.total_thickness = thickness;
|
||||
ret.left_over = thickness - std::accumulate(ret.bead_widths.cbegin(), ret.bead_widths.cend(), static_cast<coord_t>(0));
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,59 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
#define REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
/*!
|
||||
* A meta-beading-strategy that takes outer and inner wall widths into account.
|
||||
*
|
||||
* The outer wall will try to keep a constant width by only applying the beading strategy on the inner walls. This
|
||||
* ensures that this outer wall doesn't react to changes happening to inner walls. It will limit print artifacts on
|
||||
* the surface of the print. Although this strategy technically deviates from the original philosophy of the paper.
|
||||
* It will generally results in better prints because of a smoother motion and less variation in extrusion width in
|
||||
* the outer walls.
|
||||
*
|
||||
* If the thickness of the model is less then two times the optimal outer wall width and once the minimum inner wall
|
||||
* width it will keep the minimum inner wall at a minimum constant and vary the outer wall widths symmetrical. Until
|
||||
* The thickness of the model is that of at least twice the optimal outer wall width it will then use two
|
||||
* symmetrical outer walls only. Until it transitions into a single outer wall. These last scenario's are always
|
||||
* symmetrical in nature, disregarding the user specified strategy.
|
||||
*/
|
||||
class RedistributeBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* /param optimal_width_outer Outer wall width, guaranteed to be the actual (save rounding errors) at a
|
||||
* bead count if the parent strategies' optimum bead width is a weighted
|
||||
* average of the outer and inner walls at that bead count.
|
||||
* /param minimum_variable_line_ratio Minimum factor that the variable line might deviate from the optimal width.
|
||||
*/
|
||||
RedistributeBeadingStrategy(coord_t optimal_width_outer, double minimum_variable_line_ratio, BeadingStrategyPtr parent);
|
||||
|
||||
~RedistributeBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
BeadingStrategyPtr parent;
|
||||
coord_t optimal_width_outer;
|
||||
double minimum_variable_line_ratio;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // INWARD_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,86 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "WideningBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
WideningBeadingStrategy::WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width)
|
||||
: BeadingStrategy(*parent)
|
||||
, parent(std::move(parent))
|
||||
, min_input_width(min_input_width)
|
||||
, min_output_width(min_output_width)
|
||||
{
|
||||
}
|
||||
|
||||
std::string WideningBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("Widening+") + parent->toString();
|
||||
}
|
||||
|
||||
WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (thickness < optimal_width) {
|
||||
Beading ret;
|
||||
ret.total_thickness = thickness;
|
||||
if (thickness >= min_input_width) {
|
||||
ret.bead_widths.emplace_back(std::max(thickness, min_output_width));
|
||||
ret.toolpath_locations.emplace_back(thickness / 2);
|
||||
ret.left_over = 0;
|
||||
} else
|
||||
ret.left_over = thickness;
|
||||
|
||||
return ret;
|
||||
} else
|
||||
return parent->compute(thickness, bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count == 0)
|
||||
return min_input_width;
|
||||
else
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
if (thickness < min_input_width)
|
||||
return 0;
|
||||
coord_t ret = parent->getOptimalBeadCount(thickness);
|
||||
if (thickness >= min_input_width && ret < 1)
|
||||
return 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float WideningBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
std::vector<coord_t> WideningBeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
|
||||
{
|
||||
std::vector<coord_t> ret;
|
||||
ret.emplace_back(min_output_width);
|
||||
std::vector<coord_t> pret = parent->getNonlinearThicknesses(lower_bead_count);
|
||||
ret.insert(ret.end(), pret.begin(), pret.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,50 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef WIDENING_BEADING_STRATEGY_H
|
||||
#define WIDENING_BEADING_STRATEGY_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This is a meta-strategy that can be applied on any other beading strategy. If
|
||||
* the part is thinner than a single line, this strategy adjusts the part so
|
||||
* that it becomes the minimum thickness of one line.
|
||||
*
|
||||
* This way, tiny pieces that are smaller than a single line will still be
|
||||
* printed.
|
||||
*/
|
||||
class WideningBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Takes responsibility for deleting \param parent
|
||||
*/
|
||||
WideningBeadingStrategy(BeadingStrategyPtr parent, coord_t min_input_width, coord_t min_output_width);
|
||||
|
||||
~WideningBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
BeadingStrategyPtr parent;
|
||||
const coord_t min_input_width;
|
||||
const coord_t min_output_width;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // WIDENING_BEADING_STRATEGY_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,558 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_H
|
||||
#define SKELETAL_TRAPEZOIDATION_H
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <memory> // smart pointers
|
||||
#include <utility> // pair
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "utils/PolygonsSegmentIndex.hpp"
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
#include "../Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
//#define ARACHNE_DEBUG
|
||||
//#define ARACHNE_DEBUG_VORONOI
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
/*!
|
||||
* Main class of the dynamic beading strategies.
|
||||
*
|
||||
* The input polygon region is decomposed into trapezoids and represented as a half-edge data-structure.
|
||||
*
|
||||
* We determine which edges are 'central' accordinding to the transitioning_angle of the beading strategy,
|
||||
* and determine the bead count for these central regions and apply them outward when generating toolpaths. [oversimplified]
|
||||
*
|
||||
* The method can be visually explained as generating the 3D union of cones surface on the outline polygons,
|
||||
* and changing the heights along central regions of that surface so that they are flat.
|
||||
* For more info, please consult the paper "A framework for adaptive width control of dense contour-parallel toolpaths in fused
|
||||
deposition modeling" by Kuipers et al.
|
||||
* This visual explanation aid explains the use of "upward", "lower" etc,
|
||||
* i.e. the radial distance and/or the bead count are used as heights of this visualization, there is no coordinate called 'Z'.
|
||||
*
|
||||
* TODO: split this class into two:
|
||||
* 1. Class for generating the decomposition and aux functions for performing updates
|
||||
* 2. Class for editing the structure for our purposes.
|
||||
*/
|
||||
class SkeletalTrapezoidation
|
||||
{
|
||||
using graph_t = SkeletalTrapezoidationGraph;
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
using Beading = BeadingStrategy::Beading;
|
||||
using BeadingPropagation = SkeletalTrapezoidationJoint::BeadingPropagation;
|
||||
using TransitionMiddle = SkeletalTrapezoidationEdge::TransitionMiddle;
|
||||
using TransitionEnd = SkeletalTrapezoidationEdge::TransitionEnd;
|
||||
|
||||
template<typename T>
|
||||
using ptr_vector_t = std::vector<std::shared_ptr<T>>;
|
||||
|
||||
double transitioning_angle; //!< How pointy a region should be before we apply the method. Equals 180* - limit_bisector_angle
|
||||
coord_t discretization_step_size; //!< approximate size of segments when parabolic VD edges get discretized (and vertex-vertex edges)
|
||||
coord_t transition_filter_dist; //!< Filter transition mids (i.e. anchors) closer together than this
|
||||
coord_t allowed_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
coord_t beading_propagation_transition_dist; //!< When there are different beadings propagated from below and from above, use this transitioning distance
|
||||
//!< Filter areas marked as 'central' smaller than this
|
||||
inline coord_t central_filter_dist() { return scaled<coord_t>(0.02); }
|
||||
//!< Generic arithmatic inaccuracy. Only used to determine whether a transition really needs to insert an extra edge.
|
||||
inline coord_t snap_dist() { return scaled<coord_t>(0.02); }
|
||||
|
||||
/*!
|
||||
* The strategy to use to fill a certain shape with lines.
|
||||
*
|
||||
* Various BeadingStrategies are available that differ in which lines get to
|
||||
* print at their optimal width, where the play is being compensated, and
|
||||
* how the joints are handled where we transition to different numbers of
|
||||
* lines.
|
||||
*/
|
||||
const BeadingStrategy& beading_strategy;
|
||||
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using NodeSet = ankerl::unordered_dense::set<node_t*>;
|
||||
|
||||
/*!
|
||||
* Construct a new trapezoidation problem to solve.
|
||||
* \param polys The shapes to fill with walls.
|
||||
* \param beading_strategy The strategy to use to fill these shapes.
|
||||
* \param transitioning_angle Where we transition to a different number of
|
||||
* walls, how steep should this transition be? A lower angle means that the
|
||||
* transition will be longer.
|
||||
* \param discretization_step_size Since g-code can't represent smooth
|
||||
* transitions in line width, the line width must change with discretized
|
||||
* steps. This indicates how long the line segments between those steps will
|
||||
* be.
|
||||
* \param transition_filter_dist The minimum length of transitions.
|
||||
* Transitions shorter than this will be considered for dissolution.
|
||||
* \param beading_propagation_transition_dist When there are different
|
||||
* beadings propagated from below and from above, use this transitioning
|
||||
* distance.
|
||||
*/
|
||||
SkeletalTrapezoidation(const Polygons& polys,
|
||||
const BeadingStrategy& beading_strategy,
|
||||
double transitioning_angle
|
||||
, coord_t discretization_step_size
|
||||
, coord_t transition_filter_dist
|
||||
, coord_t allowed_filter_deviation
|
||||
, coord_t beading_propagation_transition_dist);
|
||||
|
||||
/*!
|
||||
* A skeletal graph through the polygons that we need to fill with beads.
|
||||
*
|
||||
* The skeletal graph represents the medial axes through each part of the
|
||||
* polygons, and the lines from these medial axes towards each vertex of the
|
||||
* polygons. The graph can be used to see what the width is of a polygon in
|
||||
* each place and where the width transitions.
|
||||
*/
|
||||
graph_t graph;
|
||||
|
||||
/*!
|
||||
* Generate the paths that the printer must extrude, to print the outlines
|
||||
* in the input polygons.
|
||||
* \param filter_outermost_central_edges Some edges are "central" but still
|
||||
* touch the outside of the polygon. If enabled, don't treat these as
|
||||
* "central" but as if it's a obtuse corner. As a result, sharp corners will
|
||||
* no longer end in a single line but will just loop.
|
||||
*/
|
||||
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
Polygons outline;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Auxiliary for referencing one transition along an edge which may contain multiple transitions
|
||||
*/
|
||||
struct TransitionMidRef
|
||||
{
|
||||
edge_t* edge;
|
||||
std::list<TransitionMiddle>::iterator transition_it;
|
||||
TransitionMidRef(edge_t* edge, std::list<TransitionMiddle>::iterator transition_it)
|
||||
: edge(edge)
|
||||
, transition_it(transition_it)
|
||||
{}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Compute the skeletal trapezoidation decomposition of the input shape.
|
||||
*
|
||||
* Compute the Voronoi Diagram (VD) and transfer all inside edges into our half-edge (HE) datastructure.
|
||||
*
|
||||
* The algorithm is currently a bit overcomplicated, because the discretization of parabolic edges is performed at the same time as all edges are being transfered,
|
||||
* which means that there is no one-to-one mapping from VD edges to HE edges.
|
||||
* Instead we map from a VD edge to the last HE edge.
|
||||
* This could be cimplified by recording the edges which should be discretized and discretizing the mafterwards.
|
||||
*
|
||||
* Another complication arises because the VD uses floating logic, which can result in zero-length segments after rounding to integers.
|
||||
* We therefore collapse edges and their whole cells afterwards.
|
||||
*/
|
||||
void constructFromPolygons(const Polygons& polys);
|
||||
|
||||
/*!
|
||||
* mapping each voronoi VD edge to the corresponding halfedge HE edge
|
||||
* In case the result segment is discretized, we map the VD edge to the *last* HE edge
|
||||
*/
|
||||
ankerl::unordered_dense::map<const VD::edge_type *, edge_t *> vd_edge_to_he_edge;
|
||||
ankerl::unordered_dense::map<const VD::vertex_type *, node_t *> vd_node_to_he_node;
|
||||
node_t &makeNode(const VD::vertex_type &vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
|
||||
|
||||
/*!
|
||||
* (Eventual) returned 'polylines per index' result (from generateToolpaths):
|
||||
*/
|
||||
std::vector<VariableWidthLines> *p_generated_toolpaths;
|
||||
|
||||
/*!
|
||||
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
|
||||
* \p prev_edge serves as input and output. May be null as input.
|
||||
*/
|
||||
void transferEdge(const Point &from, const Point &to, const VD::edge_type &vd_edge, edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point, const std::vector<Segment> &segments);
|
||||
|
||||
/*!
|
||||
* Discretize a Voronoi edge that represents the medial axis of a vertex-
|
||||
* line region or vertex-vertex region into small segments that can be
|
||||
* considered to have a straight medial axis and a linear line width
|
||||
* transition.
|
||||
*
|
||||
* The medial axis between a point and a line is a parabola. The rest of the
|
||||
* algorithm doesn't want to have to deal with parabola, so this discretises
|
||||
* the parabola into straight line segments. This is necessary if there is a
|
||||
* sharp inner corner (acts as a point) that comes close to a straight edge.
|
||||
*
|
||||
* The medial axis between a point and a point is a straight line segment.
|
||||
* However the distance from the medial axis to either of those points draws
|
||||
* a parabola as you go along the medial axis. That means that the resulting
|
||||
* line width along the medial axis would not be linearly increasing or
|
||||
* linearly decreasing, but needs to take the shape of a parabola. Instead,
|
||||
* we'll break this edge up into tiny line segments that can approximate the
|
||||
* parabola with tiny linear increases or decreases in line width.
|
||||
* \param segment The variable-width Voronoi edge to discretize.
|
||||
* \param points All vertices of the original Polygons to fill with beads.
|
||||
* \param segments All line segments of the original Polygons to fill with
|
||||
* beads.
|
||||
* \return A number of coordinates along the edge where the edge is broken
|
||||
* up into discrete pieces.
|
||||
*/
|
||||
Points discretize(const VD::edge_type& segment, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two
|
||||
* That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes
|
||||
* Otherwise if node.incident_edge = quad_start you couldnt reach quad_end.twin by normal iteration (i.e. it = it.twin.next)
|
||||
*/
|
||||
void separatePointyQuadEndNodes();
|
||||
|
||||
|
||||
// ^ init | v transitioning
|
||||
|
||||
void updateIsCentral(); // Update the "is_central" flag for each edge based on the transitioning_angle
|
||||
|
||||
/*!
|
||||
* Filter out small central areas.
|
||||
*
|
||||
* Only used to get rid of small edges which get marked as central because
|
||||
* of rounding errors because the region is so small.
|
||||
*/
|
||||
void filterCentral(coord_t max_length);
|
||||
|
||||
/*!
|
||||
* Filter central areas connected to starting_edge recursively.
|
||||
* \return Whether we should unmark this section marked as central, on the
|
||||
* way back out of the recursion.
|
||||
*/
|
||||
bool filterCentral(edge_t* starting_edge, coord_t traveled_dist, coord_t max_length);
|
||||
|
||||
/*!
|
||||
* Unmark the outermost edges directly connected to the outline, as not
|
||||
* being central.
|
||||
*
|
||||
* Only used to emulate some related literature.
|
||||
*
|
||||
* The paper shows that this function is bad for the stability of the framework.
|
||||
*/
|
||||
void filterOuterCentral();
|
||||
|
||||
/*!
|
||||
* Set bead count in central regions based on the optimal_bead_count of the
|
||||
* beading strategy.
|
||||
*/
|
||||
void updateBeadCount();
|
||||
|
||||
/*!
|
||||
* Add central regions and set bead counts where there is an end of the
|
||||
* central area and when traveling upward we get to another region with the
|
||||
* same bead count.
|
||||
*/
|
||||
void filterNoncentralRegions();
|
||||
|
||||
/*!
|
||||
* Add central regions and set bead counts for a particular edge and all of
|
||||
* its adjacent edges.
|
||||
*
|
||||
* Recursive subroutine for \ref filterNoncentralRegions().
|
||||
* \return Whether to set the bead count on the way back
|
||||
*/
|
||||
bool filterNoncentralRegions(edge_t* to_edge, coord_t bead_count, coord_t traveled_dist, coord_t max_dist);
|
||||
|
||||
/*!
|
||||
* Generate middle points of all transitions on edges.
|
||||
*
|
||||
* The transition middle points are saved in the graph itself. They are also
|
||||
* returned via the output parameter.
|
||||
* \param[out] edge_transitions A list of transitions that were generated.
|
||||
*/
|
||||
void generateTransitionMids(ptr_vector_t<std::list<TransitionMiddle>>& edge_transitions);
|
||||
|
||||
/*!
|
||||
* Removes some transition middle points.
|
||||
*
|
||||
* Transitions can be removed if there are multiple intersecting transitions
|
||||
* that are too close together. If transitions have opposite effects, both
|
||||
* are removed.
|
||||
*/
|
||||
void filterTransitionMids();
|
||||
|
||||
/*!
|
||||
* Merge transitions that are too close together.
|
||||
* \param edge_to_start Edge pointing to the node from which to start
|
||||
* traveling in all directions except along \p edge_to_start .
|
||||
* \param origin_transition The transition for which we are checking nearby
|
||||
* transitions.
|
||||
* \param traveled_dist The distance traveled before we came to
|
||||
* \p edge_to_start.to .
|
||||
* \param going_up Whether we are traveling in the upward direction as seen
|
||||
* from the \p origin_transition. If this doesn't align with the direction
|
||||
* according to the R diff on a consecutive edge we know there was a local
|
||||
* optimum.
|
||||
* \return Whether the origin transition should be dissolved.
|
||||
*/
|
||||
std::list<TransitionMidRef> dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up);
|
||||
|
||||
/*!
|
||||
* Spread a certain bead count over a region in the graph.
|
||||
* \param edge_to_start One edge of the region to spread the bead count in.
|
||||
* \param from_bead_count All edges with this bead count will be changed.
|
||||
* \param to_bead_count The new bead count for those edges.
|
||||
*/
|
||||
void dissolveBeadCountRegion(edge_t* edge_to_start, coord_t from_bead_count, coord_t to_bead_count);
|
||||
|
||||
/*!
|
||||
* Change the bead count if the given edge is at the end of a central
|
||||
* region.
|
||||
*
|
||||
* This is necessary to provide a transitioning bead count to the edges of a
|
||||
* central region to transition more smoothly from a high bead count in the
|
||||
* central region to a lower bead count at the edge.
|
||||
* \param edge_to_start One edge from a zone that needs to be filtered.
|
||||
* \param traveled_dist The distance along the edges we've traveled so far.
|
||||
* \param max_distance Don't filter beyond this range.
|
||||
* \param replacing_bead_count The new bead count for this region.
|
||||
* \return ``true`` if the bead count of this edge was changed.
|
||||
*/
|
||||
bool filterEndOfCentralTransition(edge_t* edge_to_start, coord_t traveled_dist, coord_t max_dist, coord_t replacing_bead_count);
|
||||
|
||||
/*!
|
||||
* Generate the endpoints of all transitions for all edges in the graph.
|
||||
* \param[out] edge_transition_ends The resulting transition endpoints.
|
||||
*/
|
||||
void generateAllTransitionEnds(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Also set the rest values at nodes in between the transition ends
|
||||
*/
|
||||
void applyTransitions(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Create extra edges along all edges, where it needs to transition from one
|
||||
* bead count to another.
|
||||
*
|
||||
* For example, if an edge of the graph goes from a bead count of 6 to a
|
||||
* bead count of 1, it needs to generate 5 places where the beads around
|
||||
* this line transition to a lower bead count. These are the "ribs". They
|
||||
* reach from the edge to the border of the polygon. Where the beads hit
|
||||
* those ribs the beads know to make a transition.
|
||||
*/
|
||||
void generateTransitioningRibs();
|
||||
|
||||
/*!
|
||||
* Generate the endpoints of a specific transition midpoint.
|
||||
* \param edge The edge to create transitions on.
|
||||
* \param mid_R The radius of the transition middle point.
|
||||
* \param transition_lower_bead_count The bead count at the lower end of the
|
||||
* transition.
|
||||
* \param[out] edge_transition_ends A list of endpoints to add the new
|
||||
* endpoints to.
|
||||
*/
|
||||
void generateTransitionEnds(edge_t& edge, coord_t mid_R, coord_t transition_lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Compute a single endpoint of a transition.
|
||||
* \param edge The edge to generate the endpoint for.
|
||||
* \param start_pos The position where the transition starts.
|
||||
* \param end_pos The position where the transition ends on the other side.
|
||||
* \param transition_half_length The distance to the transition middle
|
||||
* point.
|
||||
* \param start_rest The gap between the start of the transition and the
|
||||
* starting endpoint, as ratio of the inner bead width at the high end of
|
||||
* the transition.
|
||||
* \param end_rest The gap between the end of the transition and the ending
|
||||
* endpoint, as ratio of the inner bead width at the high end of the
|
||||
* transition.
|
||||
* \param transition_lower_bead_count The bead count at the lower end of the
|
||||
* transition.
|
||||
* \param[out] edge_transition_ends The list to put the resulting endpoints
|
||||
* in.
|
||||
* \return Whether the given edge is going downward (i.e. towards a thinner
|
||||
* region of the polygon).
|
||||
*/
|
||||
bool generateTransitionEnd(edge_t& edge, coord_t start_pos, coord_t end_pos, coord_t transition_half_length, double start_rest, double end_rest, coord_t transition_lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Determines whether an edge is going downwards or upwards in the graph.
|
||||
*
|
||||
* An edge is said to go "downwards" if it's going towards a narrower part
|
||||
* of the polygon. The notion of "downwards" comes from the conical
|
||||
* representation of the graph, where the polygon is filled with a cone of
|
||||
* maximum radius.
|
||||
*
|
||||
* This function works by recursively checking adjacent edges until the edge
|
||||
* is reached.
|
||||
* \param outgoing The edge to check.
|
||||
* \param traveled_dist The distance traversed so far.
|
||||
* \param transition_half_length The radius of the transition width.
|
||||
* \param lower_bead_count The bead count at the lower end of the edge.
|
||||
* \return ``true`` if this edge is going down, or ``false`` if it's going
|
||||
* up.
|
||||
*/
|
||||
bool isGoingDown(edge_t* outgoing, coord_t traveled_dist, coord_t transition_half_length, coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* Determines whether this edge marks the end of the central region.
|
||||
* \param edge The edge to check.
|
||||
* \return ``true`` if this edge goes from a central region to a non-central
|
||||
* region, or ``false`` in every other case (central to central, non-central
|
||||
* to non-central, non-central to central, or end-of-the-line).
|
||||
*/
|
||||
bool isEndOfCentral(const edge_t& edge) const;
|
||||
|
||||
/*!
|
||||
* Create extra ribs in the graph where the graph contains a parabolic arc
|
||||
* or a straight between two inner corners.
|
||||
*
|
||||
* There might be transitions there as the beads go through a narrow
|
||||
* bottleneck in the polygon.
|
||||
*/
|
||||
void generateExtraRibs();
|
||||
|
||||
// ^ transitioning ^
|
||||
|
||||
// v toolpath generation v
|
||||
|
||||
/*!
|
||||
* \param[out] segments the generated segments
|
||||
*/
|
||||
void generateSegments();
|
||||
|
||||
/*!
|
||||
* From a quad (a group of linked edges in one cell of the Voronoi), find
|
||||
* the edge pointing to the node that is furthest away from the border of the polygon.
|
||||
* \param quad_start_edge The first edge of the quad.
|
||||
* \return The edge of the quad that is furthest away from the border.
|
||||
*/
|
||||
edge_t* getQuadMaxRedgeTo(edge_t* quad_start_edge);
|
||||
|
||||
/*!
|
||||
* Propagate beading information from nodes that are closer to the edge
|
||||
* (low radius R) to nodes that are farther from the edge (high R).
|
||||
*
|
||||
* only propagate from nodes with beading info upward to nodes without beading info
|
||||
*
|
||||
* Edges are sorted by their radius, so that we can do a depth-first walk
|
||||
* without employing a recursive algorithm.
|
||||
*
|
||||
* In upward propagated beadings we store the distance traveled, so that we can merge these beadings with the downward propagated beadings in \ref propagateBeadingsDownward(.)
|
||||
*
|
||||
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
|
||||
*/
|
||||
void propagateBeadingsUpward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* propagate beading info from higher R nodes to lower R nodes
|
||||
*
|
||||
* merge with upward propagated beadings if they are encountered
|
||||
*
|
||||
* don't transfer to nodes which lie on the outline polygon
|
||||
*
|
||||
* edges are sorted so that we can do a depth-first walk without employing a recursive algorithm
|
||||
*
|
||||
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
|
||||
*/
|
||||
void propagateBeadingsDownward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref propagateBeadingsDownward(std::vector<edge_t*>&, ptr_vector_t<BeadingPropagation>&)
|
||||
*/
|
||||
void propagateBeadingsDownward(edge_t* edge_to_peak, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* Find a beading in between two other beadings.
|
||||
*
|
||||
* This creates a new beading. With this we can find the coordinates of the
|
||||
* endpoints of the actual line segments to draw.
|
||||
*
|
||||
* The parameters \p left and \p right are not actually always left or right
|
||||
* but just arbitrary directions to visually indicate the difference.
|
||||
* \param left One of the beadings to interpolate between.
|
||||
* \param ratio_left_to_whole The position within the two beadings to sample
|
||||
* an interpolation. Should be a ratio between 0 and 1.
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \param switching_radius The bead radius at which we switch from the left
|
||||
* beading to the merged beading, if the beadings have a different number of
|
||||
* beads.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const;
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref interpolate(const Beading&, Ratio, const Beading&, coord_t)
|
||||
*
|
||||
* This creates a new Beading between two beadings, assuming that both have
|
||||
* the same number of beads.
|
||||
* \param left One of the beadings to interpolate between.
|
||||
* \param ratio_left_to_whole The position within the two beadings to sample
|
||||
* an interpolation. Should be a ratio between 0 and 1.
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const;
|
||||
|
||||
/*!
|
||||
* Get the beading at a certain node of the skeletal graph, or create one if
|
||||
* it doesn't have one yet.
|
||||
*
|
||||
* This is a lazy get.
|
||||
* \param node The node to get the beading from.
|
||||
* \param node_beadings A list of all beadings for nodes.
|
||||
* \return The beading of that node.
|
||||
*/
|
||||
std::shared_ptr<BeadingPropagation> getOrCreateBeading(node_t* node, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* In case we cannot find the beading of a node, get a beading from the
|
||||
* nearest node.
|
||||
* \param node The node to attempt to get a beading from. The actual node
|
||||
* that the returned beading is from may be a different, nearby node.
|
||||
* \param max_dist The maximum distance to search for.
|
||||
* \return A beading for the node, or ``nullptr`` if there is no node nearby
|
||||
* with a beading.
|
||||
*/
|
||||
std::shared_ptr<BeadingPropagation> getNearestBeading(node_t* node, coord_t max_dist);
|
||||
|
||||
/*!
|
||||
* generate junctions for each bone
|
||||
* \param edge_to_junctions junctions ordered high R to low R
|
||||
*/
|
||||
void generateJunctions(ptr_vector_t<BeadingPropagation>& node_beadings, ptr_vector_t<LineJunctions>& edge_junctions);
|
||||
|
||||
/*!
|
||||
* Add a new toolpath segment, defined between two extrusion-juntions.
|
||||
*
|
||||
* \param from The junction from which to add a segment.
|
||||
* \param to The junction to which to add a segment.
|
||||
* \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton.
|
||||
* \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from
|
||||
* \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together.
|
||||
* \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together.
|
||||
*/
|
||||
void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way);
|
||||
|
||||
/*!
|
||||
* connect junctions in each quad
|
||||
*/
|
||||
void connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions);
|
||||
|
||||
/*!
|
||||
* Genrate small segments for local maxima where the beading would only result in a single bead
|
||||
*/
|
||||
void generateLocalMaximaSingleBeads();
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // VORONOI_QUADRILATERALIZATION_H
|
||||
@@ -0,0 +1,122 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
#define SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
|
||||
#include <memory> // smart pointers
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class SkeletalTrapezoidationEdge
|
||||
{
|
||||
private:
|
||||
enum class Central { UNKNOWN = -1, NO, YES };
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Representing the location along an edge where the anchor position of a transition should be placed.
|
||||
*/
|
||||
struct TransitionMiddle
|
||||
{
|
||||
coord_t pos; // Position along edge as measure from edge.from.p
|
||||
int lower_bead_count;
|
||||
coord_t feature_radius; // The feature radius at which this transition is placed
|
||||
TransitionMiddle(coord_t pos, int lower_bead_count, coord_t feature_radius)
|
||||
: pos(pos), lower_bead_count(lower_bead_count)
|
||||
, feature_radius(feature_radius)
|
||||
{}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Represents the location along an edge where the lower or upper end of a transition should be placed.
|
||||
*/
|
||||
struct TransitionEnd
|
||||
{
|
||||
coord_t pos; // Position along edge as measure from edge.from.p, where the edge is always the half edge oriented from lower to higher R
|
||||
int lower_bead_count;
|
||||
bool is_lower_end; // Whether this is the ed of the transition with lower bead count
|
||||
TransitionEnd(coord_t pos, int lower_bead_count, bool is_lower_end)
|
||||
: pos(pos), lower_bead_count(lower_bead_count), is_lower_end(is_lower_end)
|
||||
{}
|
||||
};
|
||||
|
||||
enum class EdgeType
|
||||
{
|
||||
NORMAL = 0, // from voronoi diagram
|
||||
EXTRA_VD = 1, // introduced to voronoi diagram in order to make the gMAT
|
||||
TRANSITION_END = 2 // introduced to voronoi diagram in order to make the gMAT
|
||||
};
|
||||
EdgeType type;
|
||||
|
||||
SkeletalTrapezoidationEdge() : SkeletalTrapezoidationEdge(EdgeType::NORMAL) {}
|
||||
SkeletalTrapezoidationEdge(const EdgeType &type) : type(type), is_central(Central::UNKNOWN) {}
|
||||
|
||||
bool isCentral() const
|
||||
{
|
||||
assert(is_central != Central::UNKNOWN);
|
||||
return is_central == Central::YES;
|
||||
}
|
||||
void setIsCentral(bool b)
|
||||
{
|
||||
is_central = b ? Central::YES : Central::NO;
|
||||
}
|
||||
bool centralIsSet() const
|
||||
{
|
||||
return is_central != Central::UNKNOWN;
|
||||
}
|
||||
|
||||
bool hasTransitions(bool ignore_empty = false) const
|
||||
{
|
||||
return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty());
|
||||
}
|
||||
void setTransitions(std::shared_ptr<std::list<TransitionMiddle>> storage)
|
||||
{
|
||||
transitions = storage;
|
||||
}
|
||||
std::shared_ptr<std::list<TransitionMiddle>> getTransitions()
|
||||
{
|
||||
return transitions.lock();
|
||||
}
|
||||
|
||||
bool hasTransitionEnds(bool ignore_empty = false) const
|
||||
{
|
||||
return transition_ends.use_count() > 0 && (ignore_empty || ! transition_ends.lock()->empty());
|
||||
}
|
||||
void setTransitionEnds(std::shared_ptr<std::list<TransitionEnd>> storage)
|
||||
{
|
||||
transition_ends = storage;
|
||||
}
|
||||
std::shared_ptr<std::list<TransitionEnd>> getTransitionEnds()
|
||||
{
|
||||
return transition_ends.lock();
|
||||
}
|
||||
|
||||
bool hasExtrusionJunctions(bool ignore_empty = false) const
|
||||
{
|
||||
return extrusion_junctions.use_count() > 0 && (ignore_empty || ! extrusion_junctions.lock()->empty());
|
||||
}
|
||||
void setExtrusionJunctions(std::shared_ptr<LineJunctions> storage)
|
||||
{
|
||||
extrusion_junctions = storage;
|
||||
}
|
||||
std::shared_ptr<LineJunctions> getExtrusionJunctions()
|
||||
{
|
||||
return extrusion_junctions.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown
|
||||
|
||||
std::weak_ptr<std::list<TransitionMiddle>> transitions;
|
||||
std::weak_ptr<std::list<TransitionEnd>> transition_ends;
|
||||
std::weak_ptr<LineJunctions> extrusion_junctions;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
@@ -0,0 +1,472 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "../Line.hpp"
|
||||
#include "libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp"
|
||||
#include "libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) : HalfEdge(data) {}
|
||||
|
||||
bool STHalfEdge::canGoUp(bool strict) const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary || strict)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Edge is between equidistqant verts; recurse!
|
||||
for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next)
|
||||
{
|
||||
if (outgoing->canGoUp())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
assert(outgoing->twin); if (!outgoing->twin) return false;
|
||||
assert(outgoing->twin->next); if (!outgoing->twin->next) return true; // This point is on the boundary?! Should never occur
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool STHalfEdge::isUpward() const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Equidistant edge case:
|
||||
std::optional<coord_t> forward_up_dist = this->distToGoUp();
|
||||
std::optional<coord_t> backward_up_dist = twin->distToGoUp();
|
||||
if (forward_up_dist && backward_up_dist)
|
||||
{
|
||||
return forward_up_dist < backward_up_dist;
|
||||
}
|
||||
|
||||
if (forward_up_dist)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (backward_up_dist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return to->p < from->p; // Arbitrary ordering, which returns the opposite for the twin edge
|
||||
}
|
||||
|
||||
std::optional<coord_t> STHalfEdge::distToGoUp() const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary)
|
||||
{
|
||||
return std::optional<coord_t>();
|
||||
}
|
||||
|
||||
// Edge is between equidistqant verts; recurse!
|
||||
std::optional<coord_t> ret;
|
||||
for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next)
|
||||
{
|
||||
std::optional<coord_t> dist_to_up = outgoing->distToGoUp();
|
||||
if (dist_to_up)
|
||||
{
|
||||
if (ret)
|
||||
{
|
||||
ret = std::min(*ret, *dist_to_up);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = dist_to_up;
|
||||
}
|
||||
}
|
||||
assert(outgoing->twin); if (!outgoing->twin) return std::optional<coord_t>();
|
||||
assert(outgoing->twin->next); if (!outgoing->twin->next) return 0; // This point is on the boundary?! Should never occur
|
||||
}
|
||||
if (ret)
|
||||
{
|
||||
ret = *ret + (to->p - from->p).cast<int64_t>().norm();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
STHalfEdge* STHalfEdge::getNextUnconnected()
|
||||
{
|
||||
edge_t* result = static_cast<STHalfEdge*>(this);
|
||||
while (result->next)
|
||||
{
|
||||
result = result->next;
|
||||
if (result == this)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return result->twin;
|
||||
}
|
||||
|
||||
STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) : HalfEdgeNode(data, p) {}
|
||||
|
||||
bool STHalfEdgeNode::isMultiIntersection()
|
||||
{
|
||||
int odd_path_count = 0;
|
||||
edge_t* outgoing = this->incident_edge;
|
||||
do
|
||||
{
|
||||
if ( ! outgoing)
|
||||
{ // This is a node on the outside
|
||||
return false;
|
||||
}
|
||||
if (outgoing->data.isCentral())
|
||||
{
|
||||
odd_path_count++;
|
||||
}
|
||||
} while (outgoing = outgoing->twin->next, outgoing != this->incident_edge);
|
||||
return odd_path_count > 2;
|
||||
}
|
||||
|
||||
bool STHalfEdgeNode::isCentral() const
|
||||
{
|
||||
edge_t* edge = incident_edge;
|
||||
do
|
||||
{
|
||||
if (edge->data.isCentral())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
assert(edge->twin); if (!edge->twin) return false;
|
||||
} while (edge = edge->twin->next, edge != incident_edge);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool STHalfEdgeNode::isLocalMaximum(bool strict) const
|
||||
{
|
||||
if (data.distance_to_boundary == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
edge_t* edge = incident_edge;
|
||||
do
|
||||
{
|
||||
if (edge->canGoUp(strict))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(edge->twin); if (!edge->twin) return false;
|
||||
|
||||
if (!edge->twin->next)
|
||||
{ // This point is on the boundary
|
||||
return false;
|
||||
}
|
||||
} while (edge = edge->twin->next, edge != incident_edge);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
|
||||
{
|
||||
ankerl::unordered_dense::map<edge_t*, Edges::iterator> edge_locator;
|
||||
ankerl::unordered_dense::map<node_t*, Nodes::iterator> node_locator;
|
||||
|
||||
for (auto edge_it = edges.begin(); edge_it != edges.end(); ++edge_it)
|
||||
{
|
||||
edge_locator.emplace(&*edge_it, edge_it);
|
||||
}
|
||||
|
||||
for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it)
|
||||
{
|
||||
node_locator.emplace(&*node_it, node_it);
|
||||
}
|
||||
|
||||
auto safelyRemoveEdge = [this, &edge_locator](edge_t* to_be_removed, Edges::iterator& current_edge_it, bool& edge_it_is_updated)
|
||||
{
|
||||
if (current_edge_it != edges.end()
|
||||
&& to_be_removed == &*current_edge_it)
|
||||
{
|
||||
current_edge_it = edges.erase(current_edge_it);
|
||||
edge_it_is_updated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
edges.erase(edge_locator[to_be_removed]);
|
||||
}
|
||||
};
|
||||
|
||||
auto should_collapse = [snap_dist](node_t* a, node_t* b)
|
||||
{
|
||||
return shorter_then(a->p - b->p, snap_dist);
|
||||
};
|
||||
|
||||
for (auto edge_it = edges.begin(); edge_it != edges.end();)
|
||||
{
|
||||
if (edge_it->prev)
|
||||
{
|
||||
edge_it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
edge_t* quad_start = &*edge_it;
|
||||
edge_t* quad_end = quad_start; while (quad_end->next) quad_end = quad_end->next;
|
||||
edge_t* quad_mid = (quad_start->next == quad_end)? nullptr : quad_start->next;
|
||||
|
||||
bool edge_it_is_updated = false;
|
||||
if (quad_mid && should_collapse(quad_mid->from, quad_mid->to))
|
||||
{
|
||||
assert(quad_mid->twin);
|
||||
if(!quad_mid->twin)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Encountered quad edge without a twin.";
|
||||
continue; //Prevent accessing unallocated memory.
|
||||
}
|
||||
int count = 0;
|
||||
for (edge_t* edge_from_3 = quad_end; edge_from_3 && edge_from_3 != quad_mid->twin; edge_from_3 = edge_from_3->twin->next)
|
||||
{
|
||||
edge_from_3->from = quad_mid->from;
|
||||
edge_from_3->twin->to = quad_mid->from;
|
||||
if (count > 50)
|
||||
{
|
||||
std::cerr << edge_from_3->from->p << " - " << edge_from_3->to->p << '\n';
|
||||
}
|
||||
if (++count > 1000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// o-o > collapse top
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// o o
|
||||
if (quad_mid->from->incident_edge == quad_mid)
|
||||
{
|
||||
if (quad_mid->twin->next)
|
||||
{
|
||||
quad_mid->from->incident_edge = quad_mid->twin->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad_mid->from->incident_edge = quad_mid->prev->twin;
|
||||
}
|
||||
}
|
||||
|
||||
nodes.erase(node_locator[quad_mid->to]);
|
||||
|
||||
quad_mid->prev->next = quad_mid->next;
|
||||
quad_mid->next->prev = quad_mid->prev;
|
||||
quad_mid->twin->next->prev = quad_mid->twin->prev;
|
||||
quad_mid->twin->prev->next = quad_mid->twin->next;
|
||||
|
||||
safelyRemoveEdge(quad_mid->twin, edge_it, edge_it_is_updated);
|
||||
safelyRemoveEdge(quad_mid, edge_it, edge_it_is_updated);
|
||||
}
|
||||
|
||||
// o-o
|
||||
// | | > collapse sides
|
||||
// o o
|
||||
if ( should_collapse(quad_start->from, quad_end->to) && should_collapse(quad_start->to, quad_end->from))
|
||||
{ // Collapse start and end edges and remove whole cell
|
||||
|
||||
quad_start->twin->to = quad_end->to;
|
||||
quad_end->to->incident_edge = quad_end->twin;
|
||||
if (quad_end->from->incident_edge == quad_end)
|
||||
{
|
||||
if (quad_end->twin->next)
|
||||
{
|
||||
quad_end->from->incident_edge = quad_end->twin->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad_end->from->incident_edge = quad_end->prev->twin;
|
||||
}
|
||||
}
|
||||
nodes.erase(node_locator[quad_start->from]);
|
||||
|
||||
quad_start->twin->twin = quad_end->twin;
|
||||
quad_end->twin->twin = quad_start->twin;
|
||||
safelyRemoveEdge(quad_start, edge_it, edge_it_is_updated);
|
||||
safelyRemoveEdge(quad_end, edge_it, edge_it_is_updated);
|
||||
}
|
||||
// If only one side had collapsable length then the cell on the other side of that edge has to collapse
|
||||
// if we would collapse that one edge then that would change the quad_start and/or quad_end of neighboring cells
|
||||
// this is to do with the constraint that !prev == !twin.next
|
||||
|
||||
if (!edge_it_is_updated)
|
||||
{
|
||||
edge_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::makeRib(edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point) {
|
||||
Point p;
|
||||
Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p);
|
||||
coord_t dist = (prev_edge->to->p - p).cast<int64_t>().norm();
|
||||
prev_edge->to->data.distance_to_boundary = dist;
|
||||
assert(dist >= 0);
|
||||
|
||||
nodes.emplace_front(SkeletalTrapezoidationJoint(), p);
|
||||
node_t* node = &nodes.front();
|
||||
node->data.distance_to_boundary = 0;
|
||||
|
||||
edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD));
|
||||
edge_t* forth_edge = &edges.front();
|
||||
edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD));
|
||||
edge_t* back_edge = &edges.front();
|
||||
|
||||
prev_edge->next = forth_edge;
|
||||
forth_edge->prev = prev_edge;
|
||||
forth_edge->from = prev_edge->to;
|
||||
forth_edge->to = node;
|
||||
forth_edge->twin = back_edge;
|
||||
back_edge->twin = forth_edge;
|
||||
back_edge->from = node;
|
||||
back_edge->to = prev_edge->to;
|
||||
node->incident_edge = back_edge;
|
||||
|
||||
prev_edge = back_edge;
|
||||
}
|
||||
|
||||
std::pair<SkeletalTrapezoidationGraph::edge_t*, SkeletalTrapezoidationGraph::edge_t*> SkeletalTrapezoidationGraph::insertRib(edge_t& edge, node_t* mid_node)
|
||||
{
|
||||
edge_t* edge_before = edge.prev;
|
||||
edge_t* edge_after = edge.next;
|
||||
node_t* node_before = edge.from;
|
||||
node_t* node_after = edge.to;
|
||||
|
||||
Point p = mid_node->p;
|
||||
|
||||
const Line source_segment = getSource(edge);
|
||||
Point px;
|
||||
source_segment.distance_to_squared(p, &px);
|
||||
coord_t dist = (p - px).cast<int64_t>().norm();
|
||||
assert(dist > 0);
|
||||
mid_node->data.distance_to_boundary = dist;
|
||||
mid_node->data.transition_ratio = 0; // Both transition end should have rest = 0, because at the ends a whole number of beads fits without rest
|
||||
|
||||
nodes.emplace_back(SkeletalTrapezoidationJoint(), px);
|
||||
node_t* source_node = &nodes.back();
|
||||
source_node->data.distance_to_boundary = 0;
|
||||
|
||||
edge_t* first = &edge;
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge());
|
||||
edge_t* second = &edges.back();
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END));
|
||||
edge_t* outward_edge = &edges.back();
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END));
|
||||
edge_t* inward_edge = &edges.back();
|
||||
|
||||
if (edge_before)
|
||||
{
|
||||
edge_before->next = first;
|
||||
}
|
||||
first->next = outward_edge;
|
||||
outward_edge->next = nullptr;
|
||||
inward_edge->next = second;
|
||||
second->next = edge_after;
|
||||
|
||||
if (edge_after)
|
||||
{
|
||||
edge_after->prev = second;
|
||||
}
|
||||
second->prev = inward_edge;
|
||||
inward_edge->prev = nullptr;
|
||||
outward_edge->prev = first;
|
||||
first->prev = edge_before;
|
||||
|
||||
first->to = mid_node;
|
||||
outward_edge->to = source_node;
|
||||
inward_edge->to = mid_node;
|
||||
second->to = node_after;
|
||||
|
||||
first->from = node_before;
|
||||
outward_edge->from = mid_node;
|
||||
inward_edge->from = source_node;
|
||||
second->from = mid_node;
|
||||
|
||||
node_before->incident_edge = first;
|
||||
mid_node->incident_edge = outward_edge;
|
||||
source_node->incident_edge = inward_edge;
|
||||
if (edge_after)
|
||||
{
|
||||
node_after->incident_edge = edge_after;
|
||||
}
|
||||
|
||||
first->data.setIsCentral(true);
|
||||
outward_edge->data.setIsCentral(false); // TODO verify this is always the case.
|
||||
inward_edge->data.setIsCentral(false);
|
||||
second->data.setIsCentral(true);
|
||||
|
||||
outward_edge->twin = inward_edge;
|
||||
inward_edge->twin = outward_edge;
|
||||
|
||||
first->twin = nullptr; // we don't know these yet!
|
||||
second->twin = nullptr;
|
||||
|
||||
assert(second->prev->from->data.distance_to_boundary == 0);
|
||||
|
||||
return std::make_pair(first, second);
|
||||
}
|
||||
|
||||
SkeletalTrapezoidationGraph::edge_t* SkeletalTrapezoidationGraph::insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count)
|
||||
{
|
||||
edge_t* last_edge_replacing_input = edge;
|
||||
|
||||
nodes.emplace_back(SkeletalTrapezoidationJoint(), mid);
|
||||
node_t* mid_node = &nodes.back();
|
||||
|
||||
edge_t* twin = last_edge_replacing_input->twin;
|
||||
last_edge_replacing_input->twin = nullptr;
|
||||
twin->twin = nullptr;
|
||||
std::pair<edge_t*, edge_t*> left_pair = insertRib(*last_edge_replacing_input, mid_node);
|
||||
std::pair<edge_t*, edge_t*> right_pair = insertRib(*twin, mid_node);
|
||||
edge_t* first_edge_replacing_input = left_pair.first;
|
||||
last_edge_replacing_input = left_pair.second;
|
||||
edge_t* first_edge_replacing_twin = right_pair.first;
|
||||
edge_t* last_edge_replacing_twin = right_pair.second;
|
||||
|
||||
first_edge_replacing_input->twin = last_edge_replacing_twin;
|
||||
last_edge_replacing_twin->twin = first_edge_replacing_input;
|
||||
last_edge_replacing_input->twin = first_edge_replacing_twin;
|
||||
first_edge_replacing_twin->twin = last_edge_replacing_input;
|
||||
|
||||
mid_node->data.bead_count = mide_node_bead_count;
|
||||
|
||||
return last_edge_replacing_input;
|
||||
}
|
||||
|
||||
Line SkeletalTrapezoidationGraph::getSource(const edge_t &edge) const
|
||||
{
|
||||
const edge_t *from_edge = &edge;
|
||||
while (from_edge->prev)
|
||||
from_edge = from_edge->prev;
|
||||
|
||||
const edge_t *to_edge = &edge;
|
||||
while (to_edge->next)
|
||||
to_edge = to_edge->next;
|
||||
|
||||
return Line(from_edge->from->p, to_edge->to->p);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_GRAPH_H
|
||||
#define SKELETAL_TRAPEZOIDATION_GRAPH_H
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/utils/HalfEdge.hpp"
|
||||
#include "libslic3r/Arachne/utils/HalfEdgeNode.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
class Line;
|
||||
class Point;
|
||||
};
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class STHalfEdgeNode;
|
||||
|
||||
class STHalfEdge : public HalfEdge<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
STHalfEdge(SkeletalTrapezoidationEdge data);
|
||||
|
||||
/*!
|
||||
* Check (recursively) whether there is any upward edge from the distance_to_boundary of the from of the \param edge
|
||||
*
|
||||
* \param strict Whether equidistant edges can count as a local maximum
|
||||
*/
|
||||
bool canGoUp(bool strict = false) const;
|
||||
|
||||
/*!
|
||||
* Check whether the edge goes from a lower to a higher distance_to_boundary.
|
||||
* Effectively deals with equidistant edges by looking beyond this edge.
|
||||
*/
|
||||
bool isUpward() const;
|
||||
|
||||
/*!
|
||||
* Calculate the traversed distance until we meet an upward edge.
|
||||
* Useful for calling on edges between equidistant points.
|
||||
*
|
||||
* If we can go up then the distance includes the length of the \param edge
|
||||
*/
|
||||
std::optional<coord_t> distToGoUp() const;
|
||||
|
||||
STHalfEdge* getNextUnconnected();
|
||||
};
|
||||
|
||||
class STHalfEdgeNode : public HalfEdgeNode<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p);
|
||||
|
||||
bool isMultiIntersection();
|
||||
|
||||
bool isCentral() const;
|
||||
|
||||
/*!
|
||||
* Check whether this node has a locally maximal distance_to_boundary
|
||||
*
|
||||
* \param strict Whether equidistant edges can count as a local maximum
|
||||
*/
|
||||
bool isLocalMaximum(bool strict = false) const;
|
||||
};
|
||||
|
||||
class SkeletalTrapezoidationGraph: public HalfEdgeGraph<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
|
||||
/*!
|
||||
* If an edge is too small, collapse it and its twin and fix the surrounding edges to ensure a consistent graph.
|
||||
*
|
||||
* Don't collapse support edges, unless we can collapse the whole quad.
|
||||
*
|
||||
* o-,
|
||||
* | "-o
|
||||
* | | > Don't collapse this edge only.
|
||||
* o o
|
||||
*/
|
||||
void collapseSmallEdges(coord_t snap_dist = 5);
|
||||
|
||||
void makeRib(edge_t*& prev_edge, const Point &start_source_point, const Point &end_source_point);
|
||||
|
||||
/*!
|
||||
* Insert a node into the graph and connect it to the input polygon using ribs
|
||||
*
|
||||
* \return the last edge which replaced [edge], which points to the same [to] node
|
||||
*/
|
||||
edge_t* insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count);
|
||||
|
||||
/*!
|
||||
* Return the first and last edge of the edges replacing \p edge pointing to the same node
|
||||
*/
|
||||
std::pair<edge_t*, edge_t*> insertRib(edge_t& edge, node_t* mid_node);
|
||||
|
||||
protected:
|
||||
Line getSource(const edge_t& edge) const;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
#define SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
|
||||
#include <memory> // smart pointers
|
||||
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class SkeletalTrapezoidationJoint
|
||||
{
|
||||
using Beading = BeadingStrategy::Beading;
|
||||
public:
|
||||
struct BeadingPropagation
|
||||
{
|
||||
Beading beading;
|
||||
coord_t dist_to_bottom_source;
|
||||
coord_t dist_from_top_source;
|
||||
bool is_upward_propagated_only;
|
||||
BeadingPropagation(const Beading& beading)
|
||||
: beading(beading)
|
||||
, dist_to_bottom_source(0)
|
||||
, dist_from_top_source(0)
|
||||
, is_upward_propagated_only(false)
|
||||
{}
|
||||
};
|
||||
|
||||
coord_t distance_to_boundary;
|
||||
coord_t bead_count;
|
||||
float transition_ratio; //! The distance near the skeleton to leave free because this joint is in the middle of a transition, as a fraction of the inner bead width of the bead at the higher transition.
|
||||
SkeletalTrapezoidationJoint()
|
||||
: distance_to_boundary(-1)
|
||||
, bead_count(-1)
|
||||
, transition_ratio(0)
|
||||
{}
|
||||
|
||||
bool hasBeading() const
|
||||
{
|
||||
return beading.use_count() > 0;
|
||||
}
|
||||
void setBeading(std::shared_ptr<BeadingPropagation> storage)
|
||||
{
|
||||
beading = storage;
|
||||
}
|
||||
std::shared_ptr<BeadingPropagation> getBeading()
|
||||
{
|
||||
return beading.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::weak_ptr<BeadingPropagation> beading;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
@@ -0,0 +1,879 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <algorithm> //For std::partition_copy and std::min_element.
|
||||
#include <unordered_set>
|
||||
|
||||
#include "WallToolPaths.hpp"
|
||||
|
||||
#include "SkeletalTrapezoidation.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "utils/SparseLineGrid.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "utils/PolylineStitcher.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
//#define ARACHNE_STITCH_PATCH_DEBUG
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfig &print_object_config, const PrintConfig &print_config)
|
||||
{
|
||||
WallToolPathsParams input_params;
|
||||
{
|
||||
const double min_nozzle_diameter = *std::min_element(print_config.nozzle_diameter.values.begin(), print_config.nozzle_diameter.values.end());
|
||||
if (const auto &min_feature_size_opt = print_object_config.min_feature_size)
|
||||
input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter;
|
||||
|
||||
if (const auto &min_wall_length_factor_opt = print_object_config.min_length_factor)
|
||||
input_params.min_length_factor = min_wall_length_factor_opt.value;
|
||||
else
|
||||
input_params.min_length_factor = 0.5f;
|
||||
|
||||
if (layer_id == 0) {
|
||||
if (const auto &initial_layer_min_bead_width_opt = print_object_config.initial_layer_min_bead_width)
|
||||
input_params.min_bead_width = initial_layer_min_bead_width_opt.value * 0.01 * min_nozzle_diameter;
|
||||
} else {
|
||||
if (const auto &min_bead_width_opt = print_object_config.min_bead_width)
|
||||
input_params.min_bead_width = min_bead_width_opt.value * 0.01 * min_nozzle_diameter;
|
||||
}
|
||||
|
||||
if (const auto &wall_transition_filter_deviation_opt = print_object_config.wall_transition_filter_deviation)
|
||||
input_params.wall_transition_filter_deviation = wall_transition_filter_deviation_opt.value * 0.01 * min_nozzle_diameter;
|
||||
|
||||
if (const auto &wall_transition_length_opt = print_object_config.wall_transition_length)
|
||||
input_params.wall_transition_length = wall_transition_length_opt.value * 0.01 * min_nozzle_diameter;
|
||||
|
||||
input_params.wall_transition_angle = print_object_config.wall_transition_angle.value;
|
||||
input_params.wall_distribution_count = print_object_config.wall_distribution_count.value;
|
||||
|
||||
input_params.is_top_or_bottom_layer = false; // Set to default value
|
||||
}
|
||||
|
||||
return input_params;
|
||||
}
|
||||
|
||||
WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x,
|
||||
const size_t inset_count, const coord_t wall_0_inset, const coordf_t layer_height, const WallToolPathsParams ¶ms)
|
||||
: outline(outline)
|
||||
, bead_width_0(bead_width_0)
|
||||
, bead_width_x(bead_width_x)
|
||||
, inset_count(inset_count)
|
||||
, wall_0_inset(wall_0_inset)
|
||||
, layer_height(layer_height)
|
||||
, print_thin_walls(Slic3r::Arachne::fill_outline_gaps)
|
||||
, min_feature_size(scaled<coord_t>(params.min_feature_size))
|
||||
, min_bead_width(scaled<coord_t>(params.min_bead_width))
|
||||
, small_area_length(static_cast<double>(bead_width_0) / 2.)
|
||||
, wall_transition_filter_deviation(scaled<coord_t>(params.wall_transition_filter_deviation))
|
||||
, toolpaths_generated(false)
|
||||
, m_params(params)
|
||||
{
|
||||
}
|
||||
|
||||
void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared)
|
||||
{
|
||||
if (thiss.size() < 3) {
|
||||
thiss.points.clear();
|
||||
return;
|
||||
}
|
||||
if (thiss.size() == 3)
|
||||
return;
|
||||
|
||||
Polygon new_path;
|
||||
Point previous = thiss.points.back();
|
||||
Point previous_previous = thiss.points.at(thiss.points.size() - 2);
|
||||
Point current = thiss.points.at(0);
|
||||
|
||||
/* When removing a vertex, we check the height of the triangle of the area
|
||||
being removed from the original polygon by the simplification. However,
|
||||
when consecutively removing multiple vertices the height of the previously
|
||||
removed vertices w.r.t. the shortcut path changes.
|
||||
In order to not recompute the new height value of previously removed
|
||||
vertices we compute the height of a representative triangle, which covers
|
||||
the same amount of area as the area being cut off. We use the Shoelace
|
||||
formula to accumulate the area under the removed segments. This works by
|
||||
computing the area in a 'fan' where each of the blades of the fan go from
|
||||
the origin to one of the segments. While removing vertices the area in
|
||||
this fan accumulates. By subtracting the area of the blade connected to
|
||||
the short-cutting segment we obtain the total area of the cutoff region.
|
||||
From this area we compute the height of the representative triangle using
|
||||
the standard formula for a triangle area: A = .5*b*h
|
||||
*/
|
||||
int64_t accumulated_area_removed = int64_t(previous.x()) * int64_t(current.y()) - int64_t(previous.y()) * int64_t(current.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
|
||||
for (size_t point_idx = 0; point_idx < thiss.points.size(); point_idx++) {
|
||||
current = thiss.points.at(point_idx % thiss.points.size());
|
||||
|
||||
//Check if the accumulated area doesn't exceed the maximum.
|
||||
Point next;
|
||||
if (point_idx + 1 < thiss.points.size()) {
|
||||
next = thiss.points.at(point_idx + 1);
|
||||
} else if (point_idx + 1 == thiss.points.size() && new_path.size() > 1) { // don't spill over if the [next] vertex will then be equal to [previous]
|
||||
next = new_path[0]; //Spill over to new polygon for checking removed area.
|
||||
} else {
|
||||
next = thiss.points.at((point_idx + 1) % thiss.points.size());
|
||||
}
|
||||
const int64_t removed_area_next = int64_t(current.x()) * int64_t(next.y()) - int64_t(current.y()) * int64_t(next.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
const int64_t negative_area_closing = int64_t(next.x()) * int64_t(previous.y()) - int64_t(next.y()) * int64_t(previous.x()); // area between the origin and the short-cutting segment
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<int64_t>(25.)) {
|
||||
// We're allowed to always delete segments of less than 5 micron.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // close the shortcut area polygon
|
||||
const int64_t base_length_2 = (next - previous).cast<int64_t>().squaredNorm();
|
||||
|
||||
if (base_length_2 == 0) //Two line segments form a line back and forth with no area.
|
||||
continue; //Remove the vertex.
|
||||
//We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
|
||||
//1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
|
||||
//A = 1/2 * b * h [triangle area formula]
|
||||
//L = b * h [apply above two and take out the 1/2]
|
||||
//h = L / b [divide by b]
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const int64_t height_2 = double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2);
|
||||
if ((height_2 <= Slic3r::sqr(scaled<coord_t>(0.005)) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current, previous, next) <= scaled<double>(0.005))) // make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
continue;
|
||||
|
||||
if (length2 < smallest_line_segment_squared
|
||||
&& height_2 <= allowed_error_distance_squared) // removing the vertex doesn't introduce too much error.)
|
||||
{
|
||||
const int64_t next_length2 = (current - next).cast<int64_t>().squaredNorm();
|
||||
if (next_length2 > 4 * smallest_line_segment_squared) {
|
||||
// Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
|
||||
// We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
|
||||
// By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
|
||||
// We just need to be sure that the intersection point does not introduce an artifact itself.
|
||||
Point intersection_point;
|
||||
bool has_intersection = Line(previous_previous, previous).intersection_infinite(Line(current, next), &intersection_point);
|
||||
if (!has_intersection
|
||||
|| Line::distance_to_infinite_squared(intersection_point, previous, current) > double(allowed_error_distance_squared)
|
||||
|| (intersection_point - previous).cast<int64_t>().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous'
|
||||
|| (intersection_point - next).cast<int64_t>().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current'
|
||||
{
|
||||
// We can't find a better spot for it, but the size of the line is more than 5 micron.
|
||||
// So the only thing we can do here is leave it in...
|
||||
}
|
||||
else {
|
||||
// New point seems like a valid one.
|
||||
current = intersection_point;
|
||||
// If there was a previous point added, remove it.
|
||||
if(!new_path.empty()) {
|
||||
new_path.points.pop_back();
|
||||
previous = previous_previous;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue; //Remove the vertex.
|
||||
}
|
||||
}
|
||||
//Don't remove the vertex.
|
||||
accumulated_area_removed = removed_area_next; // so that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = current; //Note that "previous" is only updated if we don't remove the vertex.
|
||||
new_path.points.push_back(current);
|
||||
}
|
||||
|
||||
thiss = new_path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes vertices of the polygons to make sure that they are not too high
|
||||
* resolution.
|
||||
*
|
||||
* This removes points which are connected to line segments that are shorter
|
||||
* than the `smallest_line_segment`, unless that would introduce a deviation
|
||||
* in the contour of more than `allowed_error_distance`.
|
||||
*
|
||||
* Criteria:
|
||||
* 1. Never remove a vertex if either of the connceted segments is larger than \p smallest_line_segment
|
||||
* 2. Never remove a vertex if the distance between that vertex and the final resulting polygon would be higher than \p allowed_error_distance
|
||||
* 3. The direction of segments longer than \p smallest_line_segment always
|
||||
* remains unaltered (but their end points may change if it is connected to
|
||||
* a small segment)
|
||||
*
|
||||
* Simplify uses a heuristic and doesn't neccesarily remove all removable
|
||||
* vertices under the above criteria, but simplify may never violate these
|
||||
* criteria. Unless the segments or the distance is smaller than the
|
||||
* rounding error of 5 micron.
|
||||
*
|
||||
* Vertices which introduce an error of less than 5 microns are removed
|
||||
* anyway, even if the segments are longer than the smallest line segment.
|
||||
* This makes sure that (practically) colinear line segments are joined into
|
||||
* a single line segment.
|
||||
* \param smallest_line_segment Maximal length of removed line segments.
|
||||
* \param allowed_error_distance If removing a vertex introduces a deviation
|
||||
* from the original path that is more than this distance, the vertex may
|
||||
* not be removed.
|
||||
*/
|
||||
void simplify(Polygons &thiss, const int64_t smallest_line_segment = scaled<coord_t>(0.01), const int64_t allowed_error_distance = scaled<coord_t>(0.005))
|
||||
{
|
||||
const int64_t allowed_error_distance_squared = int64_t(allowed_error_distance) * int64_t(allowed_error_distance);
|
||||
const int64_t smallest_line_segment_squared = int64_t(smallest_line_segment) * int64_t(smallest_line_segment);
|
||||
for (size_t p = 0; p < thiss.size(); p++)
|
||||
{
|
||||
simplify(thiss[p], smallest_line_segment_squared, allowed_error_distance_squared);
|
||||
if (thiss[p].size() < 3)
|
||||
{
|
||||
thiss.erase(thiss.begin() + p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator> LocToLineGrid;
|
||||
std::unique_ptr<LocToLineGrid> createLocToLineGrid(const Polygons &polygons, int square_size)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (const auto &poly : polygons)
|
||||
n_points += poly.size();
|
||||
|
||||
auto ret = std::make_unique<LocToLineGrid>(square_size, n_points);
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
for (unsigned int point_idx = 0; point_idx < polygons[poly_idx].size(); point_idx++)
|
||||
ret->insert(PolygonsPointIndex(&polygons, poly_idx, point_idx));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Note: Also tries to solve for near-self intersections, when epsilon >= 1
|
||||
*/
|
||||
void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
{
|
||||
if (epsilon < 1) {
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd);
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t half_epsilon = (epsilon + 1) / 2;
|
||||
|
||||
// Points too close to line segments should be moved a little away from those line segments, but less than epsilon,
|
||||
// so at least half-epsilon distance between points can still be guaranteed.
|
||||
const coord_t grid_size = scaled<coord_t>(2.);
|
||||
auto query_grid = createLocToLineGrid(thiss, grid_size);
|
||||
|
||||
const auto move_dist = std::max<int64_t>(2L, half_epsilon - 2);
|
||||
const int64_t half_epsilon_sqrd = half_epsilon * half_epsilon;
|
||||
|
||||
const size_t n = thiss.size();
|
||||
for (size_t poly_idx = 0; poly_idx < n; poly_idx++) {
|
||||
const size_t pathlen = thiss[poly_idx].size();
|
||||
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) {
|
||||
Point &pt = thiss[poly_idx][point_idx];
|
||||
for (const auto &line : query_grid->getNearby(pt, epsilon)) {
|
||||
const size_t line_next_idx = (line.point_idx + 1) % thiss[line.poly_idx].size();
|
||||
if (poly_idx == line.poly_idx && (point_idx == line.point_idx || point_idx == line_next_idx))
|
||||
continue;
|
||||
|
||||
const Line segment(thiss[line.poly_idx][line.point_idx], thiss[line.poly_idx][line_next_idx]);
|
||||
Point segment_closest_point;
|
||||
segment.distance_to_squared(pt, &segment_closest_point);
|
||||
|
||||
if (half_epsilon_sqrd >= (pt - segment_closest_point).cast<int64_t>().squaredNorm()) {
|
||||
const Point &other = thiss[poly_idx][(point_idx + 1) % pathlen];
|
||||
const Vec2i64 vec = (LinearAlg2D::pointIsLeftOfLine(other, segment.a, segment.b) > 0 ? segment.b - segment.a : segment.a - segment.b).cast<int64_t>();
|
||||
assert(Slic3r::sqr(double(vec.x())) < double(std::numeric_limits<int64_t>::max()));
|
||||
assert(Slic3r::sqr(double(vec.y())) < double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t len = vec.norm();
|
||||
pt.x() += (-vec.y() * move_dist) / len;
|
||||
pt.y() += (vec.x() * move_dist) / len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss), ClipperLib::pftEvenOdd);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes overlapping consecutive line segments which don't delimit a positive area.
|
||||
*/
|
||||
void removeDegenerateVerts(Polygons &thiss)
|
||||
{
|
||||
for (size_t poly_idx = 0; poly_idx < thiss.size(); poly_idx++) {
|
||||
Polygon &poly = thiss[poly_idx];
|
||||
Polygon result;
|
||||
|
||||
auto isDegenerate = [](const Point &last, const Point &now, const Point &next) {
|
||||
Vec2i64 last_line = (now - last).cast<int64_t>();
|
||||
Vec2i64 next_line = (next - now).cast<int64_t>();
|
||||
return last_line.dot(next_line) == -1 * last_line.norm() * next_line.norm();
|
||||
};
|
||||
bool isChanged = false;
|
||||
for (size_t idx = 0; idx < poly.size(); idx++) {
|
||||
const Point &last = (result.size() == 0) ? poly.back() : result.back();
|
||||
if (idx + 1 == poly.size() && result.size() == 0)
|
||||
break;
|
||||
|
||||
const Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1];
|
||||
if (isDegenerate(last, poly[idx], next)) { // lines are in the opposite direction
|
||||
// don't add vert to the result
|
||||
isChanged = true;
|
||||
while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next))
|
||||
result.points.pop_back();
|
||||
} else {
|
||||
result.points.emplace_back(poly[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
if (result.size() > 2) {
|
||||
poly = result;
|
||||
} else {
|
||||
thiss.erase(thiss.begin() + poly_idx);
|
||||
poly_idx--; // effectively the next iteration has the same poly_idx (referring to a new poly which is not yet processed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool remove_holes)
|
||||
{
|
||||
auto to_path = [](const Polygon &poly) -> ClipperLib::Path {
|
||||
ClipperLib::Path out;
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y()));
|
||||
return out;
|
||||
};
|
||||
|
||||
auto new_end = thiss.end();
|
||||
if (remove_holes) {
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector.
|
||||
if (fabs(ClipperLib::Area(to_path(*it))) < min_area_size) {
|
||||
--new_end;
|
||||
*it = std::move(*new_end);
|
||||
continue; // Don't increment the iterator such that the polygon just swapped in is checked next.
|
||||
}
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
// For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes
|
||||
Polygons small_holes;
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
if (double area = ClipperLib::Area(to_path(*it)); fabs(area) < min_area_size) {
|
||||
if (area >= 0) {
|
||||
--new_end;
|
||||
if (it < new_end) {
|
||||
std::swap(*new_end, *it);
|
||||
continue;
|
||||
} else { // Don't self-swap the last Path
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
small_holes.push_back(*it);
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
// Removes small holes that have their first point inside one of the removed outlines
|
||||
// Iterating in reverse ensures that unprocessed small holes won't be moved
|
||||
const auto removed_outlines_start = new_end;
|
||||
for (auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++)
|
||||
for (auto outline_it = removed_outlines_start; outline_it < thiss.end(); outline_it++)
|
||||
if (Polygon(*outline_it).contains(*hole_it->begin())) {
|
||||
new_end--;
|
||||
*hole_it = std::move(*new_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
thiss.resize(new_end-thiss.begin());
|
||||
}
|
||||
|
||||
void removeColinearEdges(Polygon &poly, const double max_deviation_angle)
|
||||
{
|
||||
// TODO: Can be made more efficient (for example, use pointer-types for process-/skip-indices, so we can swap them without copy).
|
||||
size_t num_removed_in_iteration = 0;
|
||||
do {
|
||||
num_removed_in_iteration = 0;
|
||||
std::vector<bool> process_indices(poly.points.size(), true);
|
||||
|
||||
bool go = true;
|
||||
while (go) {
|
||||
go = false;
|
||||
|
||||
const auto &rpath = poly;
|
||||
const size_t pathlen = rpath.size();
|
||||
if (pathlen <= 3)
|
||||
return;
|
||||
|
||||
std::vector<bool> skip_indices(poly.points.size(), false);
|
||||
|
||||
Polygon new_path;
|
||||
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) {
|
||||
// Don't iterate directly over process-indices, but do it this way, because there are points _in_ process-indices that should nonetheless
|
||||
// be skipped:
|
||||
if (!process_indices[point_idx]) {
|
||||
new_path.points.push_back(rpath[point_idx]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Should skip the last point for this iteration if the old first was removed (which can be seen from the fact that the new first was skipped):
|
||||
if (point_idx == (pathlen - 1) && skip_indices[0]) {
|
||||
skip_indices[new_path.size()] = true;
|
||||
go = true;
|
||||
new_path.points.push_back(rpath[point_idx]);
|
||||
break;
|
||||
}
|
||||
|
||||
const Point &prev = rpath[(point_idx - 1 + pathlen) % pathlen];
|
||||
const Point &pt = rpath[point_idx];
|
||||
const Point &next = rpath[(point_idx + 1) % pathlen];
|
||||
|
||||
float angle = LinearAlg2D::getAngleLeft(prev, pt, next); // [0 : 2 * pi]
|
||||
if (angle >= float(M_PI)) { angle -= float(M_PI); } // map [pi : 2 * pi] to [0 : pi]
|
||||
|
||||
// Check if the angle is within limits for the point to 'make sense', given the maximum deviation.
|
||||
// If the angle indicates near-parallel segments ignore the point 'pt'
|
||||
if (angle > max_deviation_angle && angle < M_PI - max_deviation_angle) {
|
||||
new_path.points.push_back(pt);
|
||||
} else if (point_idx != (pathlen - 1)) {
|
||||
// Skip the next point, since the current one was removed:
|
||||
skip_indices[new_path.size()] = true;
|
||||
go = true;
|
||||
new_path.points.push_back(next);
|
||||
++point_idx;
|
||||
}
|
||||
}
|
||||
poly = new_path;
|
||||
num_removed_in_iteration += pathlen - poly.points.size();
|
||||
|
||||
process_indices.clear();
|
||||
process_indices.insert(process_indices.end(), skip_indices.begin(), skip_indices.end());
|
||||
}
|
||||
} while (num_removed_in_iteration > 0);
|
||||
}
|
||||
|
||||
void removeColinearEdges(Polygons &thiss, const double max_deviation_angle = 0.0005)
|
||||
{
|
||||
for (int p = 0; p < int(thiss.size()); p++) {
|
||||
removeColinearEdges(thiss[p], max_deviation_angle);
|
||||
if (thiss[p].size() < 3) {
|
||||
thiss.erase(thiss.begin() + p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
||||
{
|
||||
if (this->inset_count < 1)
|
||||
return toolpaths;
|
||||
|
||||
const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution();
|
||||
const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation();
|
||||
const coord_t epsilon_offset = (allowed_distance / 2) - 1;
|
||||
const double transitioning_angle = Geometry::deg2rad(m_params.wall_transition_angle);
|
||||
const coord_t discretization_step_size = scaled<coord_t>(0.8);
|
||||
|
||||
// Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed:
|
||||
// TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons?
|
||||
Polygons prepared_outline = offset(offset(offset(outline, -epsilon_offset), epsilon_offset * 2), -epsilon_offset);
|
||||
simplify(prepared_outline, smallest_segment, allowed_distance);
|
||||
fixSelfIntersections(epsilon_offset, prepared_outline);
|
||||
removeDegenerateVerts(prepared_outline);
|
||||
removeColinearEdges(prepared_outline, 0.005);
|
||||
// Removing collinear edges may introduce self intersections, so we need to fix them again
|
||||
fixSelfIntersections(epsilon_offset, prepared_outline);
|
||||
removeDegenerateVerts(prepared_outline);
|
||||
removeSmallAreas(prepared_outline, small_area_length * small_area_length, false);
|
||||
|
||||
// The functions above could produce intersecting polygons that could cause a crash inside Arachne.
|
||||
// Applying Clipper union should be enough to get rid of this issue.
|
||||
// Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges
|
||||
// didn't have twin edges. (a non-planar Voronoi diagram probably caused this).
|
||||
prepared_outline = union_(prepared_outline);
|
||||
|
||||
if (area(prepared_outline) <= 0) {
|
||||
assert(toolpaths.empty());
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
const float external_perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_0), float(this->layer_height));
|
||||
const float perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_x), float(this->layer_height));
|
||||
|
||||
const coord_t wall_transition_length = scaled<coord_t>(this->m_params.wall_transition_length);
|
||||
|
||||
const double wall_split_middle_threshold = std::clamp(2. * unscaled<double>(this->min_bead_width) / external_perimeter_extrusion_width - 1., 0.01, 0.99); // For an uneven nr. of lines: When to split the middle wall into two.
|
||||
const double wall_add_middle_threshold = std::clamp(unscaled<double>(this->min_bead_width) / perimeter_extrusion_width, 0.01, 0.99); // For an even nr. of lines: When to add a new middle in between the innermost two walls.
|
||||
|
||||
const int wall_distribution_count = this->m_params.wall_distribution_count;
|
||||
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
|
||||
const auto beading_strat = BeadingStrategyFactory::makeStrategy
|
||||
(
|
||||
bead_width_0,
|
||||
bead_width_x,
|
||||
wall_transition_length,
|
||||
transitioning_angle,
|
||||
print_thin_walls,
|
||||
min_bead_width,
|
||||
min_feature_size,
|
||||
wall_split_middle_threshold,
|
||||
wall_add_middle_threshold,
|
||||
max_bead_count,
|
||||
wall_0_inset,
|
||||
wall_distribution_count
|
||||
);
|
||||
const coord_t transition_filter_dist = scaled<coord_t>(100.f);
|
||||
const coord_t allowed_filter_deviation = wall_transition_filter_deviation;
|
||||
SkeletalTrapezoidation wall_maker
|
||||
(
|
||||
prepared_outline,
|
||||
*beading_strat,
|
||||
beading_strat->getTransitioningAngle(),
|
||||
discretization_step_size,
|
||||
transition_filter_dist,
|
||||
allowed_filter_deviation,
|
||||
wall_transition_length
|
||||
);
|
||||
wall_maker.generateToolpaths(toolpaths);
|
||||
|
||||
stitchToolPaths(toolpaths, this->bead_width_x);
|
||||
|
||||
removeSmallLines(toolpaths);
|
||||
|
||||
separateOutInnerContour();
|
||||
|
||||
simplifyToolPaths(toolpaths);
|
||||
|
||||
removeEmptyToolPaths(toolpaths);
|
||||
assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(),
|
||||
[](const VariableWidthLines& l, const VariableWidthLines& r)
|
||||
{
|
||||
return l.front().inset_idx < r.front().inset_idx;
|
||||
}) && "WallToolPaths should be sorted from the outer 0th to inner_walls");
|
||||
toolpaths_generated = true;
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, const coord_t bead_width_x)
|
||||
{
|
||||
const coord_t stitch_distance = bead_width_x - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width.
|
||||
|
||||
for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) {
|
||||
VariableWidthLines& wall_lines = toolpaths[wall_idx];
|
||||
|
||||
VariableWidthLines stitched_polylines;
|
||||
VariableWidthLines closed_polygons;
|
||||
PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance);
|
||||
#ifdef ARACHNE_STITCH_PATCH_DEBUG
|
||||
for (const ExtrusionLine& line : stitched_polylines) {
|
||||
if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Some even contour lines could not be closed into polygons!";
|
||||
assert(false && "Some even contour lines could not be closed into polygons!");
|
||||
BoundingBox aabb;
|
||||
for (auto line2 : wall_lines)
|
||||
for (auto j : line2)
|
||||
aabb.merge(j.p);
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("contours_before.svg-%d.png", iRun), aabb);
|
||||
std::array<const char *, 8> colors = {"gray", "black", "blue", "green", "lime", "purple", "red", "yellow"};
|
||||
size_t color_idx = 0;
|
||||
for (auto& inset : toolpaths)
|
||||
for (auto& line2 : inset) {
|
||||
// svg.writePolyline(line2.toPolygon(), col);
|
||||
|
||||
Polygon poly = line2.toPolygon();
|
||||
Point last = poly.front();
|
||||
for (size_t idx = 1 ; idx < poly.size(); idx++) {
|
||||
Point here = poly[idx];
|
||||
svg.draw(Line(last, here), colors[color_idx]);
|
||||
// svg.draw_text((last + here) / 2, std::to_string(line2.junctions[idx].region_id).c_str(), "black");
|
||||
last = here;
|
||||
}
|
||||
svg.draw(poly[0], colors[color_idx]);
|
||||
// svg.nextLayer();
|
||||
// svg.writePoints(poly, true, 0.1);
|
||||
// svg.nextLayer();
|
||||
color_idx = (color_idx + 1) % colors.size();
|
||||
}
|
||||
}
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("contours-%d.svg", iRun), aabb);
|
||||
for (auto& inset : toolpaths)
|
||||
for (auto& line2 : inset)
|
||||
svg.draw_outline(line2.toPolygon(), "gray");
|
||||
for (auto& line2 : stitched_polylines) {
|
||||
const char *col = line2.is_odd ? "gray" : "red";
|
||||
if ( ! line2.is_odd)
|
||||
std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n";
|
||||
if ( ! line2.is_odd)
|
||||
svg.draw(line2.front().p);
|
||||
Polygon poly = line2.toPolygon();
|
||||
Point last = poly.front();
|
||||
for (size_t idx = 1 ; idx < poly.size(); idx++)
|
||||
{
|
||||
Point here = poly[idx];
|
||||
svg.draw(Line(last, here), col);
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
for (auto line2 : closed_polygons)
|
||||
svg.draw(line2.toPolygon());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ARACHNE_STITCH_PATCH_DEBUG
|
||||
wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines
|
||||
|
||||
for (ExtrusionLine& wall_polygon : closed_polygons)
|
||||
{
|
||||
if (wall_polygon.junctions.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// PolylineStitcher, in some cases, produced closed extrusion (polygons),
|
||||
// but the endpoints differ by a small distance. So we reconnect them.
|
||||
// FIXME Lukas H.: Investigate more deeply why it is happening.
|
||||
if (wall_polygon.junctions.front().p != wall_polygon.junctions.back().p &&
|
||||
(wall_polygon.junctions.back().p - wall_polygon.junctions.front().p).cast<double>().norm() < stitch_distance) {
|
||||
wall_polygon.junctions.emplace_back(wall_polygon.junctions.front());
|
||||
}
|
||||
wall_polygon.is_closed = true;
|
||||
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
|
||||
}
|
||||
#ifdef DEBUG
|
||||
for (ExtrusionLine& line : wall_lines)
|
||||
{
|
||||
assert(line.inset_idx == wall_idx);
|
||||
}
|
||||
#endif // DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> bool shorterThan(const T &shape, const coord_t check_length)
|
||||
{
|
||||
const auto *p0 = &shape.back();
|
||||
int64_t length = 0;
|
||||
for (const auto &p1 : shape) {
|
||||
length += (*p0 - p1).template cast<int64_t>().norm();
|
||||
if (length >= check_length)
|
||||
return false;
|
||||
p0 = &p1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WallToolPaths::removeSmallLines(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
for (VariableWidthLines &inset : toolpaths) {
|
||||
for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) {
|
||||
ExtrusionLine &line = inset[line_idx];
|
||||
coord_t min_width = std::numeric_limits<coord_t>::max();
|
||||
for (const ExtrusionJunction &j : line)
|
||||
min_width = std::min(min_width, j.w);
|
||||
// Only use min_length_factor for non-topmost, to prevent top gaps. Otherwise use default value.
|
||||
if (line.is_odd && !line.is_closed && shorterThan(line, m_params.is_top_or_bottom_layer ? (min_width / 2) : (min_width * m_params.min_length_factor))) { // remove line
|
||||
line = std::move(inset.back());
|
||||
inset.erase(--inset.end());
|
||||
line_idx--; // reconsider the current position
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx)
|
||||
{
|
||||
const int64_t maximum_resolution = Slic3r::Arachne::meshfix_maximum_resolution();
|
||||
const int64_t maximum_deviation = Slic3r::Arachne::meshfix_maximum_deviation();
|
||||
const int64_t maximum_extrusion_area_deviation = Slic3r::Arachne::meshfix_maximum_extrusion_area_deviation(); // unit: μm²
|
||||
for (auto& line : toolpaths[toolpaths_idx])
|
||||
{
|
||||
line.simplify(maximum_resolution * maximum_resolution, maximum_deviation * maximum_deviation, maximum_extrusion_area_deviation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VariableWidthLines> &WallToolPaths::getToolPaths()
|
||||
{
|
||||
if (!toolpaths_generated)
|
||||
return generate();
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
void WallToolPaths::separateOutInnerContour()
|
||||
{
|
||||
//We'll remove all 0-width paths from the original toolpaths and store them separately as polygons.
|
||||
std::vector<VariableWidthLines> actual_toolpaths;
|
||||
actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude.
|
||||
std::vector<VariableWidthLines> contour_paths;
|
||||
contour_paths.reserve(toolpaths.size() / inset_count);
|
||||
inner_contour.clear();
|
||||
for (const VariableWidthLines &inset : toolpaths) {
|
||||
if (inset.empty())
|
||||
continue;
|
||||
bool is_contour = false;
|
||||
for (const ExtrusionLine &line : inset) {
|
||||
for (const ExtrusionJunction &j : line) {
|
||||
if (j.w == 0)
|
||||
is_contour = true;
|
||||
else
|
||||
is_contour = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_contour) {
|
||||
#ifdef DEBUG
|
||||
for (const ExtrusionLine &line : inset)
|
||||
for (const ExtrusionJunction &j : line)
|
||||
assert(j.w == 0);
|
||||
#endif // DEBUG
|
||||
for (const ExtrusionLine &line : inset) {
|
||||
if (line.is_odd)
|
||||
continue; // odd lines don't contribute to the contour
|
||||
else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon
|
||||
inner_contour.emplace_back(line.toPolygon());
|
||||
}
|
||||
} else {
|
||||
actual_toolpaths.emplace_back(inset);
|
||||
}
|
||||
}
|
||||
if (!actual_toolpaths.empty())
|
||||
toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths.
|
||||
else
|
||||
toolpaths.clear();
|
||||
|
||||
//The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines.
|
||||
//They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative.
|
||||
//To get a correct shape, we need to make the outside contour positive and any holes inside negative.
|
||||
//This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon.
|
||||
//The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation.
|
||||
inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd);
|
||||
}
|
||||
|
||||
const Polygons& WallToolPaths::getInnerContour()
|
||||
{
|
||||
if (!toolpaths_generated && inset_count > 0)
|
||||
{
|
||||
generate();
|
||||
}
|
||||
else if(inset_count == 0)
|
||||
{
|
||||
return outline;
|
||||
}
|
||||
return inner_contour;
|
||||
}
|
||||
|
||||
bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines)
|
||||
{
|
||||
return lines.empty();
|
||||
}), toolpaths.end());
|
||||
return toolpaths.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector<ExtrusionLine *> &input, const bool outer_to_inner)
|
||||
{
|
||||
ExtrusionLineSet order_requirements;
|
||||
// We build a grid where we map toolpath vertex locations to toolpaths,
|
||||
// so that we can easily find which two toolpaths are next to each other,
|
||||
// which is the requirement for there to be an order constraint.
|
||||
//
|
||||
// We use a PointGrid rather than a LineGrid to save on computation time.
|
||||
// In very rare cases two insets might lie next to each other without having neighboring vertices, e.g.
|
||||
// \ .
|
||||
// | / .
|
||||
// | / .
|
||||
// || .
|
||||
// | \ .
|
||||
// | \ .
|
||||
// / .
|
||||
// However, because of how Arachne works this will likely never be the case for two consecutive insets.
|
||||
// On the other hand one could imagine that two consecutive insets of a very large circle
|
||||
// could be simplify()ed such that the remaining vertices of the two insets don't align.
|
||||
// In those cases the order requirement is not captured,
|
||||
// which means that the PathOrderOptimizer *might* result in a violation of the user set path order.
|
||||
// This problem is expected to be not so severe and happen very sparsely.
|
||||
|
||||
coord_t max_line_w = 0u;
|
||||
for (const ExtrusionLine *line : input) // compute max_line_w
|
||||
for (const ExtrusionJunction &junction : *line)
|
||||
max_line_w = std::max(max_line_w, junction.w);
|
||||
if (max_line_w == 0u)
|
||||
return order_requirements;
|
||||
|
||||
struct LineLoc
|
||||
{
|
||||
ExtrusionJunction j;
|
||||
const ExtrusionLine *line;
|
||||
};
|
||||
struct Locator
|
||||
{
|
||||
Point operator()(const LineLoc &elem) { return elem.j.p; }
|
||||
};
|
||||
|
||||
// How much farther two verts may be apart due to corners.
|
||||
// This distance must be smaller than 2, because otherwise
|
||||
// we could create an order requirement between e.g.
|
||||
// wall 2 of one region and wall 3 of another region,
|
||||
// while another wall 3 of the first region would lie in between those two walls.
|
||||
// However, higher values are better against the limitations of using a PointGrid rather than a LineGrid.
|
||||
constexpr float diagonal_extension = 1.9f;
|
||||
const auto searching_radius = coord_t(max_line_w * diagonal_extension);
|
||||
using GridT = SparsePointGrid<LineLoc, Locator>;
|
||||
GridT grid(searching_radius);
|
||||
|
||||
for (const ExtrusionLine *line : input)
|
||||
for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line});
|
||||
for (const std::pair<const SquareGrid::GridPoint, LineLoc> &pair : grid) {
|
||||
const LineLoc &lineloc_here = pair.second;
|
||||
const ExtrusionLine *here = lineloc_here.line;
|
||||
Point loc_here = pair.second.j.p;
|
||||
std::vector<LineLoc> nearby_verts = grid.getNearby(loc_here, searching_radius);
|
||||
for (const LineLoc &lineloc_nearby : nearby_verts) {
|
||||
const ExtrusionLine *nearby = lineloc_nearby.line;
|
||||
if (nearby == here)
|
||||
continue;
|
||||
if (nearby->inset_idx == here->inset_idx)
|
||||
continue;
|
||||
if (nearby->inset_idx > here->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (here->inset_idx > nearby->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
||||
continue; // points are too far away from each other
|
||||
if (here->is_odd || nearby->is_odd) {
|
||||
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
} else {
|
||||
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
}
|
||||
}
|
||||
}
|
||||
return order_requirements;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef CURAENGINE_WALLTOOLPATHS_H
|
||||
#define CURAENGINE_WALLTOOLPATHS_H
|
||||
|
||||
#include <memory>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "BeadingStrategy/BeadingStrategyFactory.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
constexpr bool fill_outline_gaps = true;
|
||||
inline coord_t meshfix_maximum_resolution() { return scaled<coord_t>(0.5); }
|
||||
inline coord_t meshfix_maximum_deviation() { return scaled<coord_t>(0.025); }
|
||||
inline coord_t meshfix_maximum_extrusion_area_deviation() { return scaled<coord_t>(2.); }
|
||||
|
||||
class WallToolPathsParams
|
||||
{
|
||||
public:
|
||||
float min_bead_width;
|
||||
float min_feature_size;
|
||||
float min_length_factor;
|
||||
float wall_transition_length;
|
||||
float wall_transition_angle;
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
bool is_top_or_bottom_layer;
|
||||
};
|
||||
|
||||
WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfig &print_object_config, const PrintConfig &print_config);
|
||||
|
||||
class WallToolPaths
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* A class that creates the toolpaths given an outline, nominal bead width and maximum amount of walls
|
||||
* \param outline An outline of the area in which the ToolPaths are to be generated
|
||||
* \param bead_width_0 The bead width of the first wall used in the generation of the toolpaths
|
||||
* \param bead_width_x The bead width of the inner walls used in the generation of the toolpaths
|
||||
* \param inset_count The maximum number of parallel extrusion lines that make up the wall
|
||||
* \param wall_0_inset How far to inset the outer wall, to make it adhere better to other walls.
|
||||
*/
|
||||
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, coordf_t layer_height, const WallToolPathsParams ¶ms);
|
||||
|
||||
/*!
|
||||
* Generates the Toolpaths
|
||||
* \return A reference to the newly create ToolPaths
|
||||
*/
|
||||
const std::vector<VariableWidthLines> &generate();
|
||||
|
||||
/*!
|
||||
* Gets the toolpaths, if this called before \p generate() it will first generate the Toolpaths
|
||||
* \return a reference to the toolpaths
|
||||
*/
|
||||
const std::vector<VariableWidthLines> &getToolPaths();
|
||||
|
||||
/*!
|
||||
* Compute the inner contour of the walls. This contour indicates where the walled area ends and its infill begins.
|
||||
* The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of
|
||||
* infill with extra infill walls.
|
||||
*/
|
||||
void separateOutInnerContour();
|
||||
|
||||
/*!
|
||||
* Gets the inner contour of the area which is inside of the generated tool
|
||||
* paths.
|
||||
*
|
||||
* If the walls haven't been generated yet, this will lazily call the
|
||||
* \p generate() function to generate the walls with variable width.
|
||||
* The resulting polygon will snugly match the inside of the variable-width
|
||||
* walls where the walls get limited by the LimitedBeadingStrategy to a
|
||||
* maximum wall count.
|
||||
* If there are no walls, the outline will be returned.
|
||||
* \return The inner contour of the generated walls.
|
||||
*/
|
||||
const Polygons& getInnerContour();
|
||||
|
||||
/*!
|
||||
* Removes empty paths from the toolpaths
|
||||
* \param toolpaths the VariableWidthPaths generated with \p generate()
|
||||
* \return true if there are still paths left. If all toolpaths were removed it returns false
|
||||
*/
|
||||
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
using ExtrusionLineSet = ankerl::unordered_dense::set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>>;
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
static ExtrusionLineSet getRegionOrder(const std::vector<ExtrusionLine *> &input, bool outer_to_inner);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Stitch the polylines together and form closed polygons.
|
||||
*
|
||||
* Works on both toolpaths and inner contours simultaneously.
|
||||
*/
|
||||
static void stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, coord_t bead_width_x);
|
||||
|
||||
/*!
|
||||
* Remove polylines shorter than half the smallest line width along that polyline.
|
||||
*/
|
||||
void removeSmallLines(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
/*!
|
||||
* Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided
|
||||
* settings.
|
||||
* \param settings The settings as provided by the user
|
||||
* \return
|
||||
*/
|
||||
static void simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
private:
|
||||
const Polygons& outline; //<! A reference to the outline polygon that is the designated area
|
||||
coord_t bead_width_0; //<! The nominal or first extrusion line width with which libArachne generates its walls
|
||||
coord_t bead_width_x; //<! The subsequently extrusion line width with which libArachne generates its walls if WallToolPaths was called with the nominal_bead_width Constructor this is the same as bead_width_0
|
||||
size_t inset_count; //<! The maximum number of walls to generate
|
||||
coord_t wall_0_inset; //<! How far to inset the outer wall. Should only be applied when printing the actual walls, not extra infill/skin/support walls.
|
||||
coordf_t layer_height;
|
||||
bool print_thin_walls; //<! Whether to enable the widening beading meta-strategy for thin features
|
||||
coord_t min_feature_size; //<! The minimum size of the features that can be widened by the widening beading meta-strategy. Features thinner than that will not be printed
|
||||
coord_t min_bead_width; //<! The minimum bead size to use when widening thin model features with the widening beading meta-strategy
|
||||
double small_area_length; //<! The length of the small features which are to be filtered out, this is squared into a surface
|
||||
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
bool toolpaths_generated; //<! Are the toolpaths generated
|
||||
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
|
||||
Polygons inner_contour; //<! The inner contour of the generated toolpaths
|
||||
const WallToolPathsParams m_params;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // CURAENGINE_WALLTOOLPATHS_H
|
||||
@@ -0,0 +1,66 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_EXTRUSION_JUNCTION_H
|
||||
#define UTILS_EXTRUSION_JUNCTION_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This struct represents one vertex in an extruded path.
|
||||
*
|
||||
* It contains information on how wide the extruded path must be at this point,
|
||||
* and which perimeter it represents.
|
||||
*/
|
||||
struct ExtrusionJunction
|
||||
{
|
||||
/*!
|
||||
* The position of the centreline of the path when it reaches this junction.
|
||||
* This is the position that should end up in the g-code eventually.
|
||||
*/
|
||||
Point p;
|
||||
|
||||
/*!
|
||||
* The width of the extruded path at this junction.
|
||||
*/
|
||||
coord_t w;
|
||||
|
||||
/*!
|
||||
* Which perimeter this junction is part of.
|
||||
*
|
||||
* Perimeters are counted from the outside inwards. The outer wall has index
|
||||
* 0.
|
||||
*/
|
||||
size_t perimeter_index;
|
||||
|
||||
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
|
||||
|
||||
bool operator==(const ExtrusionJunction &other) const {
|
||||
return p == other.p && w == other.w && perimeter_index == other.perimeter_index;
|
||||
}
|
||||
|
||||
coord_t x() const { return p.x(); }
|
||||
coord_t y() const { return p.y(); }
|
||||
coord_t z() const { return w; }
|
||||
};
|
||||
|
||||
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)
|
||||
{
|
||||
return a.p - b.p;
|
||||
}
|
||||
|
||||
// Identity function, used to be able to make templated algorithms that do their operations on 'point-like' input.
|
||||
inline const Point& make_point(const ExtrusionJunction& ej)
|
||||
{
|
||||
return ej.p;
|
||||
}
|
||||
|
||||
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
|
||||
using ExtrusionJunctions = std::vector<ExtrusionJunction>;
|
||||
|
||||
}
|
||||
#endif // UTILS_EXTRUSION_JUNCTION_H
|
||||
@@ -0,0 +1,298 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "ExtrusionLine.hpp"
|
||||
#include "../../VariableWidth.hpp"
|
||||
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class Flow;
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {}
|
||||
|
||||
int64_t ExtrusionLine::getLength() const
|
||||
{
|
||||
if (junctions.empty())
|
||||
return 0;
|
||||
|
||||
int64_t len = 0;
|
||||
ExtrusionJunction prev = junctions.front();
|
||||
for (const ExtrusionJunction &next : junctions) {
|
||||
len += (next.p - prev.p).cast<int64_t>().norm();
|
||||
prev = next;
|
||||
}
|
||||
if (is_closed)
|
||||
len += (front().p - back().p).cast<int64_t>().norm();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation)
|
||||
{
|
||||
const size_t min_path_size = is_closed ? 3 : 2;
|
||||
if (junctions.size() <= min_path_size)
|
||||
return;
|
||||
|
||||
/* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
|
||||
* starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
|
||||
* should not touch the first and last points. As a result, start simplifying from point at index 1.
|
||||
* */
|
||||
std::vector<ExtrusionJunction> new_junctions;
|
||||
// Starting junction should always exist in the simplified path
|
||||
new_junctions.emplace_back(junctions.front());
|
||||
|
||||
ExtrusionJunction previous = junctions.front();
|
||||
/* For open ExtrusionLines the last junction cannot be taken into consideration when checking the points at index 1.
|
||||
* For closed ExtrusionLines, the first and last junctions are the same, so use the prior to last juction.
|
||||
* */
|
||||
ExtrusionJunction previous_previous = this->is_closed ? junctions[junctions.size() - 2] : junctions.front();
|
||||
|
||||
/* TODO: When deleting, combining, or modifying junctions, it would
|
||||
* probably be good to set the new junction's width to a weighted average
|
||||
* of the junctions it is derived from.
|
||||
*/
|
||||
|
||||
/* When removing a vertex, we check the height of the triangle of the area
|
||||
being removed from the original polygon by the simplification. However,
|
||||
when consecutively removing multiple vertices the height of the previously
|
||||
removed vertices w.r.t. the shortcut path changes.
|
||||
In order to not recompute the new height value of previously removed
|
||||
vertices we compute the height of a representative triangle, which covers
|
||||
the same amount of area as the area being cut off. We use the Shoelace
|
||||
formula to accumulate the area under the removed segments. This works by
|
||||
computing the area in a 'fan' where each of the blades of the fan go from
|
||||
the origin to one of the segments. While removing vertices the area in
|
||||
this fan accumulates. By subtracting the area of the blade connected to
|
||||
the short-cutting segment we obtain the total area of the cutoff region.
|
||||
From this area we compute the height of the representative triangle using
|
||||
the standard formula for a triangle area: A = .5*b*h
|
||||
*/
|
||||
const ExtrusionJunction& initial = junctions[1];
|
||||
int64_t accumulated_area_removed = int64_t(previous.p.x()) * int64_t(initial.p.y()) - int64_t(previous.p.y()) * int64_t(initial.p.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
|
||||
// For a closed polygon we process the last point, which is the same as the first point.
|
||||
for (size_t point_idx = 1; point_idx < junctions.size() - (this->is_closed ? 0 : 1); point_idx++)
|
||||
{
|
||||
// For the last point of a closed polygon, use the first point of the new polygon in case we modified it.
|
||||
const bool is_last = point_idx + 1 == junctions.size();
|
||||
const ExtrusionJunction& current = is_last ? new_junctions[0] : junctions[point_idx];
|
||||
|
||||
// Don't simplify closed polygons below 3 junctions.
|
||||
if (this->is_closed && new_junctions.size() + (junctions.size() - point_idx) <= 3) {
|
||||
new_junctions.push_back(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
|
||||
const bool spill_over = this->is_closed && point_idx + 2 >= junctions.size() &&
|
||||
point_idx + 2 - junctions.size() < new_junctions.size();
|
||||
ExtrusionJunction& next = spill_over ? new_junctions[point_idx + 2 - junctions.size()] : junctions[point_idx + 1];
|
||||
|
||||
const int64_t removed_area_next = int64_t(current.p.x()) * int64_t(next.p.y()) - int64_t(current.p.y()) * int64_t(next.p.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
const int64_t negative_area_closing = int64_t(next.p.x()) * int64_t(previous.p.y()) - int64_t(next.p.y()) * int64_t(previous.p.x()); // Area between the origin and the short-cutting segment
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<coord_t>(0.025))
|
||||
{
|
||||
// We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
|
||||
const int64_t base_length_2 = (next - previous).cast<int64_t>().squaredNorm();
|
||||
|
||||
if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
|
||||
{
|
||||
continue; // Remove the junction (vertex).
|
||||
}
|
||||
//We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
|
||||
//1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
|
||||
//A = 1/2 * b * h [triangle area formula]
|
||||
//L = b * h [apply above two and take out the 1/2]
|
||||
//h = L / b [divide by b]
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const auto height_2 = int64_t(double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2));
|
||||
const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next);
|
||||
if ((height_2 <= scaled<coord_t>(0.001) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled<double>(0.001)) // Make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
// We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
|
||||
&& extrusion_area_error <= maximum_extrusion_area_deviation)
|
||||
{
|
||||
// Remove the current junction (vertex).
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length2 < smallest_line_segment_squared
|
||||
&& height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
|
||||
{
|
||||
const int64_t next_length2 = (current - next).cast<int64_t>().squaredNorm();
|
||||
if (next_length2 > 4 * smallest_line_segment_squared)
|
||||
{
|
||||
// Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
|
||||
// We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
|
||||
// By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
|
||||
// We just need to be sure that the intersection point does not introduce an artifact itself.
|
||||
// o < prev_prev
|
||||
// |
|
||||
// o < prev
|
||||
// \ < short segment
|
||||
// intersection > + o-------------------o < next
|
||||
// ^ current
|
||||
Point intersection_point;
|
||||
bool has_intersection = Line(previous_previous.p, previous.p).intersection_infinite(Line(current.p, next.p), &intersection_point);
|
||||
const auto dist_greater = [](const Point& p1, const Point& p2, const int64_t threshold) {
|
||||
const auto vec = (p1 - p2).cwiseAbs().cast<uint64_t>().eval();
|
||||
if(vec.x() > threshold || vec.y() > threshold) {
|
||||
// If this condition is true, the distance is definitely greater than the threshold.
|
||||
// We don't need to calculate the squared norm at all, which avoid potential arithmetic overflow.
|
||||
return true;
|
||||
}
|
||||
return vec.squaredNorm() > threshold;
|
||||
};
|
||||
if (!has_intersection
|
||||
|| Line::distance_to_infinite_squared(intersection_point, previous.p, current.p) > double(allowed_error_distance_squared)
|
||||
|| dist_greater(intersection_point, previous.p, smallest_line_segment_squared) // The intersection point is way too far from the 'previous'
|
||||
|| dist_greater(intersection_point, current.p, smallest_line_segment_squared)) // and 'current' points, so it shouldn't replace 'current'
|
||||
{
|
||||
// We can't find a better spot for it, but the size of the line is more than 5 micron.
|
||||
// So the only thing we can do here is leave it in...
|
||||
}
|
||||
else
|
||||
{
|
||||
// New point seems like a valid one.
|
||||
const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
|
||||
// If there was a previous point added, remove it.
|
||||
if(!new_junctions.empty())
|
||||
{
|
||||
new_junctions.pop_back();
|
||||
previous = previous_previous;
|
||||
}
|
||||
|
||||
// The junction (vertex) is replaced by the new one.
|
||||
accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
|
||||
new_junctions.push_back(new_to_add);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // Remove the junction (vertex).
|
||||
}
|
||||
}
|
||||
// The junction (vertex) isn't removed.
|
||||
accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
|
||||
new_junctions.push_back(current);
|
||||
}
|
||||
|
||||
if (this->is_closed) {
|
||||
/* The first and last points should be the same for a closed polygon.
|
||||
* We processed the last point above, so copy it into the first point.
|
||||
*/
|
||||
new_junctions.front().p = new_junctions.back().p;
|
||||
} else {
|
||||
// Ending junction (vertex) should always exist in the simplified path
|
||||
new_junctions.emplace_back(junctions.back());
|
||||
}
|
||||
|
||||
junctions = new_junctions;
|
||||
}
|
||||
|
||||
int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C) {
|
||||
/*
|
||||
* A B C A C
|
||||
* --------------- **************
|
||||
* | | ------------------------------------------
|
||||
* | |--------------------------| B removed | |***************************|
|
||||
* | | | ---------> | | |
|
||||
* | |--------------------------| | |***************************|
|
||||
* | | ------------------------------------------
|
||||
* --------------- ^ **************
|
||||
* ^ B.w + C.w / 2 ^
|
||||
* A.w + B.w / 2 new_width = weighted_average_width
|
||||
*
|
||||
*
|
||||
* ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
|
||||
* weighted-average width for the entire extrusion line.
|
||||
*
|
||||
* */
|
||||
const int64_t ab_length = (B.p - A.p).cast<int64_t>().norm();
|
||||
const int64_t bc_length = (C.p - B.p).cast<int64_t>().norm();
|
||||
if (const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w)); width_diff > 1) {
|
||||
// Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
|
||||
// weighted average value.
|
||||
const int64_t ab_weight = (A.w + B.w) / 2;
|
||||
const int64_t bc_weight = (B.w + C.w) / 2;
|
||||
const int64_t weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (ab_length + bc_length);
|
||||
const int64_t ac_length = (C.p - A.p).cast<int64_t>().norm();
|
||||
return std::abs((ab_weight * ab_length + bc_weight * bc_length) - (weighted_average_width * ac_length));
|
||||
} else {
|
||||
// If the width difference is very small, then select the width of the segment that is longer
|
||||
return ab_length > bc_length ? int64_t(width_diff) * bc_length : int64_t(width_diff) * ab_length;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExtrusionLine::is_contour() const
|
||||
{
|
||||
if (!this->is_closed)
|
||||
return false;
|
||||
|
||||
Polygon poly;
|
||||
poly.points.reserve(this->junctions.size());
|
||||
for (const ExtrusionJunction &junction : this->junctions)
|
||||
poly.points.emplace_back(junction.p);
|
||||
|
||||
// Arachne produces contour with clockwise orientation and holes with counterclockwise orientation.
|
||||
return poly.is_clockwise();
|
||||
}
|
||||
|
||||
double ExtrusionLine::area() const
|
||||
{
|
||||
assert(this->is_closed);
|
||||
double a = 0.;
|
||||
if (this->junctions.size() >= 3) {
|
||||
Vec2d p1 = this->junctions.back().p.cast<double>();
|
||||
for (const ExtrusionJunction &junction : this->junctions) {
|
||||
Vec2d p2 = junction.p.cast<double>();
|
||||
a += cross2(p1, p2);
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
return 0.5 * a;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) {
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path);
|
||||
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
|
||||
}
|
||||
}
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion);
|
||||
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
|
||||
}
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,294 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_EXTRUSION_LINE_H
|
||||
#define UTILS_EXTRUSION_LINE_H
|
||||
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
|
||||
#include "ExtrusionJunction.hpp"
|
||||
#include "../../Polyline.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "../../BoundingBox.hpp"
|
||||
#include "../../ExtrusionEntity.hpp"
|
||||
#include "../../Flow.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class ThickPolyline;
|
||||
class Flow;
|
||||
}
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Represents a polyline (not just a line) that is to be extruded with variable
|
||||
* line width.
|
||||
*
|
||||
* This polyline is a sequence of \ref ExtrusionJunction, with a bit of metadata
|
||||
* about which inset it represents.
|
||||
*/
|
||||
struct ExtrusionLine
|
||||
{
|
||||
/*!
|
||||
* Which inset this path represents, counted from the outside inwards.
|
||||
*
|
||||
* The outer wall has index 0.
|
||||
*/
|
||||
size_t inset_idx;
|
||||
|
||||
/*!
|
||||
* If a thin piece needs to be printed with an odd number of walls (e.g. 5
|
||||
* walls) then there will be one wall in the middle that is not a loop. This
|
||||
* field indicates whether this path is such a line through the middle, that
|
||||
* has no companion line going back on the other side and is not a closed
|
||||
* loop.
|
||||
*/
|
||||
bool is_odd;
|
||||
|
||||
/*!
|
||||
* Whether this is a closed polygonal path
|
||||
*/
|
||||
bool is_closed;
|
||||
|
||||
/*!
|
||||
* Gets the number of vertices in this polygon.
|
||||
* \return The number of vertices in this polygon.
|
||||
*/
|
||||
size_t size() const { return junctions.size(); }
|
||||
|
||||
/*!
|
||||
* Whether there are no junctions.
|
||||
*/
|
||||
bool empty() const { return junctions.empty(); }
|
||||
|
||||
/*!
|
||||
* The list of vertices along which this path runs.
|
||||
*
|
||||
* Each junction has a width, making this path a variable-width path.
|
||||
*/
|
||||
std::vector<ExtrusionJunction> junctions;
|
||||
|
||||
ExtrusionLine(const size_t inset_idx, const bool is_odd);
|
||||
ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {}
|
||||
ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {}
|
||||
|
||||
ExtrusionLine &operator=(ExtrusionLine &&other)
|
||||
{
|
||||
junctions = std::move(other.junctions);
|
||||
inset_idx = other.inset_idx;
|
||||
is_odd = other.is_odd;
|
||||
is_closed = other.is_closed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExtrusionLine &operator=(const ExtrusionLine &other)
|
||||
{
|
||||
junctions = other.junctions;
|
||||
inset_idx = other.inset_idx;
|
||||
is_odd = other.is_odd;
|
||||
is_closed = other.is_closed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<ExtrusionJunction>::const_iterator begin() const { return junctions.begin(); }
|
||||
std::vector<ExtrusionJunction>::const_iterator end() const { return junctions.end(); }
|
||||
std::vector<ExtrusionJunction>::const_reverse_iterator rbegin() const { return junctions.rbegin(); }
|
||||
std::vector<ExtrusionJunction>::const_reverse_iterator rend() const { return junctions.rend(); }
|
||||
std::vector<ExtrusionJunction>::const_reference front() const { return junctions.front(); }
|
||||
std::vector<ExtrusionJunction>::const_reference back() const { return junctions.back(); }
|
||||
const ExtrusionJunction &operator[](unsigned int index) const { return junctions[index]; }
|
||||
ExtrusionJunction &operator[](unsigned int index) { return junctions[index]; }
|
||||
std::vector<ExtrusionJunction>::iterator begin() { return junctions.begin(); }
|
||||
std::vector<ExtrusionJunction>::iterator end() { return junctions.end(); }
|
||||
std::vector<ExtrusionJunction>::reference front() { return junctions.front(); }
|
||||
std::vector<ExtrusionJunction>::reference back() { return junctions.back(); }
|
||||
|
||||
template<typename... Args> void emplace_back(Args &&...args) { junctions.emplace_back(args...); }
|
||||
void remove(unsigned int index) { junctions.erase(junctions.begin() + index); }
|
||||
void insert(size_t index, const ExtrusionJunction &p) { junctions.insert(junctions.begin() + index, p); }
|
||||
|
||||
template<class iterator>
|
||||
std::vector<ExtrusionJunction>::iterator insert(std::vector<ExtrusionJunction>::const_iterator pos, iterator first, iterator last)
|
||||
{
|
||||
return junctions.insert(pos, first, last);
|
||||
}
|
||||
|
||||
void clear() { junctions.clear(); }
|
||||
void reverse() { std::reverse(junctions.begin(), junctions.end()); }
|
||||
|
||||
/*!
|
||||
* Sum the total length of this path.
|
||||
*/
|
||||
int64_t getLength() const;
|
||||
int64_t polylineLength() const { return getLength(); }
|
||||
|
||||
/*!
|
||||
* Put all junction locations into a polygon object.
|
||||
*
|
||||
* When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon.
|
||||
*/
|
||||
Polygon toPolygon() const
|
||||
{
|
||||
Polygon ret;
|
||||
for (const ExtrusionJunction &j : junctions)
|
||||
ret.points.emplace_back(j.p);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes vertices of the ExtrusionLines to make sure that they are not too high
|
||||
* resolution.
|
||||
*
|
||||
* This removes junctions which are connected to line segments that are shorter
|
||||
* than the `smallest_line_segment`, unless that would introduce a deviation
|
||||
* in the contour of more than `allowed_error_distance`.
|
||||
*
|
||||
* Criteria:
|
||||
* 1. Never remove a junction if either of the connected segments is larger than \p smallest_line_segment
|
||||
* 2. Never remove a junction if the distance between that junction and the final resulting polygon would be higher
|
||||
* than \p allowed_error_distance
|
||||
* 3. The direction of segments longer than \p smallest_line_segment always
|
||||
* remains unaltered (but their end points may change if it is connected to
|
||||
* a small segment)
|
||||
* 4. Never remove a junction if it has a distinctively different width than the next junction, as this can
|
||||
* introduce unwanted irregularities on the wall widths.
|
||||
*
|
||||
* Simplify uses a heuristic and doesn't necessarily remove all removable
|
||||
* vertices under the above criteria, but simplify may never violate these
|
||||
* criteria. Unless the segments or the distance is smaller than the
|
||||
* rounding error of 5 micron.
|
||||
*
|
||||
* Vertices which introduce an error of less than 5 microns are removed
|
||||
* anyway, even if the segments are longer than the smallest line segment.
|
||||
* This makes sure that (practically) co-linear line segments are joined into
|
||||
* a single line segment.
|
||||
* \param smallest_line_segment Maximal length of removed line segments.
|
||||
* \param allowed_error_distance If removing a vertex introduces a deviation
|
||||
* from the original path that is more than this distance, the vertex may
|
||||
* not be removed.
|
||||
* \param maximum_extrusion_area_deviation The maximum extrusion area deviation allowed when removing intermediate
|
||||
* junctions from a straight ExtrusionLine
|
||||
*/
|
||||
void simplify(int64_t smallest_line_segment_squared, int64_t allowed_error_distance_squared, int64_t maximum_extrusion_area_deviation);
|
||||
|
||||
/*!
|
||||
* Computes and returns the total area error (in μm²) of the AB and BC segments of an ABC straight ExtrusionLine
|
||||
* when the junction B with a width B.w is removed from the ExtrusionLine. The area changes due to the fact that the
|
||||
* new simplified line AC has a uniform width which equals to the weighted average of the width of the subsegments
|
||||
* (based on their length).
|
||||
*
|
||||
* \param A Start point of the 3-point-straight line
|
||||
* \param B Intermediate point of the 3-point-straight line
|
||||
* \param C End point of the 3-point-straight line
|
||||
* */
|
||||
static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C);
|
||||
|
||||
bool is_contour() const;
|
||||
|
||||
double area() const;
|
||||
};
|
||||
|
||||
template<class PathType>
|
||||
static inline Slic3r::ThickPolyline to_thick_polyline(const PathType &path)
|
||||
{
|
||||
assert(path.size() >= 2);
|
||||
Slic3r::ThickPolyline out;
|
||||
out.points.emplace_back(path.front().x(), path.front().y());
|
||||
out.width.emplace_back(path.front().z());
|
||||
out.points.emplace_back(path[1].x(), path[1].y());
|
||||
out.width.emplace_back(path[1].z());
|
||||
|
||||
auto it_prev = path.begin() + 1;
|
||||
for (auto it = path.begin() + 2; it != path.end(); ++it) {
|
||||
out.points.emplace_back(it->x(), it->y());
|
||||
out.width.emplace_back(it_prev->z());
|
||||
out.width.emplace_back(it->z());
|
||||
it_prev = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline Polygon to_polygon(const ExtrusionLine &line)
|
||||
{
|
||||
Polygon out;
|
||||
assert(line.junctions.size() >= 3);
|
||||
assert(line.junctions.front().p == line.junctions.back().p);
|
||||
out.points.reserve(line.junctions.size() - 1);
|
||||
for (auto it = line.junctions.begin(); it != line.junctions.end() - 1; ++it)
|
||||
out.points.emplace_back(it->p);
|
||||
return out;
|
||||
}
|
||||
|
||||
static Points to_points(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(extrusion_line.junctions.size());
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
points.emplace_back(junction.p);
|
||||
return points;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static BoundingBox get_extents(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
bbox.merge(junction.p);
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBox get_extents(const std::vector<ExtrusionLine> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionLine &extrusion_line : extrusion_lines)
|
||||
bbox.merge(get_extents(extrusion_line));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBox get_extents(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionLine *extrusion_line : extrusion_lines) {
|
||||
assert(extrusion_line != nullptr);
|
||||
bbox.merge(get_extents(*extrusion_line));
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static std::vector<Points> to_points(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
std::vector<Points> points;
|
||||
for (const ExtrusionLine *extrusion_line : extrusion_lines) {
|
||||
assert(extrusion_line != nullptr);
|
||||
points.emplace_back(to_points(*extrusion_line));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
#endif
|
||||
|
||||
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow);
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // UTILS_EXTRUSION_LINE_H
|
||||
@@ -0,0 +1,39 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_H
|
||||
#define UTILS_HALF_EDGE_H
|
||||
|
||||
#include <forward_list>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdgeNode;
|
||||
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdge
|
||||
{
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
public:
|
||||
edge_data_t data;
|
||||
edge_t* twin = nullptr;
|
||||
edge_t* next = nullptr;
|
||||
edge_t* prev = nullptr;
|
||||
node_t* from = nullptr;
|
||||
node_t* to = nullptr;
|
||||
HalfEdge(edge_data_t data)
|
||||
: data(data)
|
||||
{}
|
||||
bool operator==(const edge_t& other)
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_H
|
||||
@@ -0,0 +1,31 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_GRAPH_H
|
||||
#define UTILS_HALF_EDGE_GRAPH_H
|
||||
|
||||
|
||||
#include <list>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
|
||||
#include "HalfEdge.hpp"
|
||||
#include "HalfEdgeNode.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
template<class node_data_t, class edge_data_t, class derived_node_t, class derived_edge_t> // types of data contained in nodes and edges
|
||||
class HalfEdgeGraph
|
||||
{
|
||||
public:
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
using Edges = std::list<edge_t>;
|
||||
using Nodes = std::list<node_t>;
|
||||
Edges edges;
|
||||
Nodes nodes;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_GRAPH_H
|
||||
@@ -0,0 +1,38 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_NODE_H
|
||||
#define UTILS_HALF_EDGE_NODE_H
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdge;
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdgeNode
|
||||
{
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
public:
|
||||
node_data_t data;
|
||||
Point p;
|
||||
edge_t* incident_edge = nullptr;
|
||||
HalfEdgeNode(node_data_t data, Point p)
|
||||
: data(data)
|
||||
, p(p)
|
||||
{}
|
||||
|
||||
bool operator==(const node_t& other)
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_NODE_H
|
||||
@@ -0,0 +1,178 @@
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYGONS_POINT_INDEX_H
|
||||
#define UTILS_POLYGONS_POINT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
// Identity function, used to be able to make templated algorithms where the input is sometimes points, sometimes things that contain or can be converted to points.
|
||||
inline const Point &make_point(const Point &p) { return p; }
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
template<typename Paths>
|
||||
class PathsPointIndex
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The polygons into which this index is indexing.
|
||||
*/
|
||||
const Paths* polygons; // (pointer to const polygons)
|
||||
|
||||
unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons
|
||||
|
||||
unsigned int point_idx; //!< The index of the point in the polygon in \ref PolygonsPointIndex::polygons
|
||||
|
||||
/*!
|
||||
* Constructs an empty point index to no polygon.
|
||||
*
|
||||
* This is used as a placeholder for when there is a zero-construction
|
||||
* needed. Since the `polygons` field is const you can't ever make this
|
||||
* initialisation useful.
|
||||
*/
|
||||
PathsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {}
|
||||
|
||||
/*!
|
||||
* Constructs a new point index to a vertex of a polygon.
|
||||
* \param polygons The Polygons instance to which this index points.
|
||||
* \param poly_idx The index of the sub-polygon to point to.
|
||||
* \param point_idx The index of the vertex in the sub-polygon.
|
||||
*/
|
||||
PathsPointIndex(const Paths *polygons, unsigned int poly_idx, unsigned int point_idx) : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {}
|
||||
|
||||
/*!
|
||||
* Copy constructor to copy these indices.
|
||||
*/
|
||||
PathsPointIndex(const PathsPointIndex& original) = default;
|
||||
|
||||
Point p() const
|
||||
{
|
||||
if (!polygons)
|
||||
return {0, 0};
|
||||
|
||||
return make_point((*polygons)[poly_idx][point_idx]);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether this point is initialised.
|
||||
*/
|
||||
bool initialized() const { return polygons; }
|
||||
|
||||
/*!
|
||||
* Get the polygon to which this PolygonsPointIndex refers
|
||||
*/
|
||||
const Polygon &getPolygon() const { return (*polygons)[poly_idx]; }
|
||||
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
* \param other The PolygonsPointIndex to test for equality
|
||||
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
|
||||
*/
|
||||
bool operator==(const PathsPointIndex &other) const
|
||||
{
|
||||
return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx;
|
||||
}
|
||||
bool operator!=(const PathsPointIndex &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
bool operator<(const PathsPointIndex &other) const
|
||||
{
|
||||
return this->p() < other.p();
|
||||
}
|
||||
PathsPointIndex &operator=(const PathsPointIndex &other)
|
||||
{
|
||||
polygons = other.polygons;
|
||||
poly_idx = other.poly_idx;
|
||||
point_idx = other.point_idx;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PathsPointIndex &operator++()
|
||||
{
|
||||
point_idx = (point_idx + 1) % (*polygons)[poly_idx].size();
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PathsPointIndex &operator--()
|
||||
{
|
||||
if (point_idx == 0)
|
||||
point_idx = (*polygons)[poly_idx].size();
|
||||
point_idx--;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PathsPointIndex next() const
|
||||
{
|
||||
PathsPointIndex ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PathsPointIndex prev() const
|
||||
{
|
||||
PathsPointIndex ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndex = PathsPointIndex<Polygons>;
|
||||
|
||||
/*!
|
||||
* Locator to extract a line segment out of a \ref PolygonsPointIndex
|
||||
*/
|
||||
struct PolygonsPointIndexSegmentLocator
|
||||
{
|
||||
std::pair<Point, Point> operator()(const PolygonsPointIndex &val) const
|
||||
{
|
||||
const Polygon &poly = (*val.polygons)[val.poly_idx];
|
||||
Point start = poly[val.point_idx];
|
||||
unsigned int next_point_idx = (val.point_idx + 1) % poly.size();
|
||||
Point end = poly[next_point_idx];
|
||||
return std::pair<Point, Point>(start, end);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Locator of a \ref PolygonsPointIndex
|
||||
*/
|
||||
template<typename Paths>
|
||||
struct PathsPointIndexLocator
|
||||
{
|
||||
Point operator()(const PathsPointIndex<Paths>& val) const
|
||||
{
|
||||
return make_point(val.p());
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* Hash function for \ref PolygonsPointIndex
|
||||
*/
|
||||
template <>
|
||||
struct hash<Slic3r::Arachne::PolygonsPointIndex>
|
||||
{
|
||||
size_t operator()(const Slic3r::Arachne::PolygonsPointIndex& lpi) const
|
||||
{
|
||||
return Slic3r::PointHash{}(lpi.p());
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
|
||||
#endif//UTILS_POLYGONS_POINT_INDEX_H
|
||||
@@ -0,0 +1,50 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
#define UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
class PolygonsSegmentIndex : public PolygonsPointIndex
|
||||
{
|
||||
public:
|
||||
PolygonsSegmentIndex() : PolygonsPointIndex(){};
|
||||
PolygonsSegmentIndex(const Polygons *polygons, unsigned int poly_idx, unsigned int point_idx) : PolygonsPointIndex(polygons, poly_idx, point_idx){};
|
||||
|
||||
Point from() const { return PolygonsPointIndex::p(); }
|
||||
|
||||
Point to() const { return PolygonsSegmentIndex::next().p(); }
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace boost::polygon {
|
||||
|
||||
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
|
||||
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
|
||||
{
|
||||
return dir.to_int() ? CSegment.to() : CSegment.from();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost::polygon
|
||||
|
||||
#endif//UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
@@ -0,0 +1,51 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "PolylineStitcher.hpp"
|
||||
|
||||
#include "ExtrusionLine.hpp"
|
||||
#include "libslic3r/Arachne/utils/PolygonsPointIndex.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Arachne {
|
||||
struct ExtrusionJunction;
|
||||
} // namespace Arachne
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canReverse(const PathsPointIndex<VariableWidthLines> &ppi)
|
||||
{
|
||||
if ((*ppi.polygons)[ppi.poly_idx].is_odd)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canReverse(const PathsPointIndex<Polygons> &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canConnect(const ExtrusionLine &a, const ExtrusionLine &b)
|
||||
{
|
||||
return a.is_odd == b.is_odd;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canConnect(const Polygon &, const Polygon &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::isOdd(const ExtrusionLine &line)
|
||||
{
|
||||
return line.is_odd;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::isOdd(const Polygon &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,243 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYLINE_STITCHER_H
|
||||
#define UTILS_POLYLINE_STITCHER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
|
||||
#include "SparsePointGrid.hpp"
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for stitching polylines into longer polylines or into polygons
|
||||
*/
|
||||
template<typename Paths, typename Path, typename Junction>
|
||||
class PolylineStitcher
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Stitch together the separate \p lines into \p result_lines and if they
|
||||
* can be closed into \p result_polygons.
|
||||
*
|
||||
* Only introduce new segments shorter than \p max_stitch_distance, and
|
||||
* larger than \p snap_distance but always try to take the shortest
|
||||
* connection possible.
|
||||
*
|
||||
* Only stitch polylines into closed polygons if they are larger than 3 *
|
||||
* \p max_stitch_distance, in order to prevent small segments to
|
||||
* accidentally get closed into a polygon.
|
||||
*
|
||||
* \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not
|
||||
* be closed into polygons.
|
||||
*
|
||||
* \note Resulting polylines and polygons are added onto the existing
|
||||
* containers, so you can directly output onto a polygons container with
|
||||
* existing polygons in it. However, you shouldn't call this function with
|
||||
* the same parameter in \p lines as \p result_lines, because that would
|
||||
* duplicate (some of) the polylines.
|
||||
* \param lines The lines to stitch together.
|
||||
* \param result_lines[out] The stitched parts that are not closed polygons
|
||||
* will be stored in here.
|
||||
* \param result_polygons[out] The stitched parts that were closed as
|
||||
* polygons will be stored in here.
|
||||
* \param max_stitch_distance The maximum distance that will be bridged to
|
||||
* connect two lines.
|
||||
* \param snap_distance Points closer than this distance are considered to
|
||||
* be the same point.
|
||||
*/
|
||||
static void stitch(const Paths& lines, Paths& result_lines, Paths& result_polygons, coord_t max_stitch_distance = scaled<coord_t>(0.1), coord_t snap_distance = scaled<coord_t>(0.01))
|
||||
{
|
||||
if (lines.empty())
|
||||
return;
|
||||
|
||||
SparsePointGrid<PathsPointIndex<Paths>, PathsPointIndexLocator<Paths>> grid(max_stitch_distance, lines.size() * 2);
|
||||
|
||||
// populate grid
|
||||
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
|
||||
{
|
||||
const auto line = lines[line_idx];
|
||||
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, 0));
|
||||
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, line.size() - 1));
|
||||
}
|
||||
|
||||
std::vector<bool> processed(lines.size(), false);
|
||||
|
||||
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
|
||||
{
|
||||
if (processed[line_idx])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
processed[line_idx] = true;
|
||||
const auto line = lines[line_idx];
|
||||
bool should_close = isOdd(line);
|
||||
|
||||
Path chain = line;
|
||||
bool closest_is_closing_polygon = false;
|
||||
for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation.
|
||||
{ // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed.
|
||||
if (go_in_reverse_direction)
|
||||
{ // try extending chain in the other direction
|
||||
chain.reverse();
|
||||
}
|
||||
int64_t chain_length = chain.polylineLength();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Point from = make_point(chain.back());
|
||||
|
||||
PathsPointIndex<Paths> closest;
|
||||
coord_t closest_distance = std::numeric_limits<coord_t>::max();
|
||||
grid.processNearby(from, max_stitch_distance,
|
||||
std::function<bool (const PathsPointIndex<Paths>&)> (
|
||||
[from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, &chain_length, go_in_reverse_direction, max_stitch_distance, snap_distance, should_close]
|
||||
(const PathsPointIndex<Paths>& nearby)->bool
|
||||
{
|
||||
bool is_closing_segment = false;
|
||||
coord_t dist = (nearby.p().template cast<int64_t>() - from.template cast<int64_t>()).norm();
|
||||
if (dist > max_stitch_distance)
|
||||
{
|
||||
return true; // keep looking
|
||||
}
|
||||
if ((nearby.p().template cast<int64_t>() - make_point(chain.front()).template cast<int64_t>()).squaredNorm() < snap_distance * snap_distance)
|
||||
{
|
||||
if (chain_length + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline
|
||||
|| chain.size() <= 2) // don't make 2 vert polygons
|
||||
{
|
||||
return true; // look for a better next line
|
||||
}
|
||||
is_closing_segment = true;
|
||||
if (!should_close)
|
||||
{
|
||||
dist += scaled<coord_t>(0.01); // prefer continuing polyline over closing a polygon; avoids closed zigzags from being printed separately
|
||||
// continue to see if closing segment is also the closest
|
||||
// there might be a segment smaller than [max_stitch_distance] which closes the polygon better
|
||||
}
|
||||
else
|
||||
{
|
||||
dist -= scaled<coord_t>(0.01); //Prefer closing the polygon if it's 100% even lines. Used to create closed contours.
|
||||
//Continue to see if closing segment is also the closest.
|
||||
}
|
||||
}
|
||||
else if (processed[nearby.poly_idx])
|
||||
{ // it was already moved to output
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
bool nearby_would_be_reversed = nearby.point_idx != 0;
|
||||
nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction
|
||||
if (!canReverse(nearby) && nearby_would_be_reversed)
|
||||
{ // connecting the segment would reverse the polygon direction
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx]))
|
||||
{
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
if (dist < closest_distance)
|
||||
{
|
||||
closest_distance = dist;
|
||||
closest = nearby;
|
||||
closest_is_closing_polygon = is_closing_segment;
|
||||
}
|
||||
if (dist < snap_distance)
|
||||
{ // we have found a good enough next line
|
||||
return false; // stop looking for alternatives
|
||||
}
|
||||
return true; // keep processing elements
|
||||
})
|
||||
);
|
||||
|
||||
if (!closest.initialized() // we couldn't find any next line
|
||||
|| closest_is_closing_polygon // we closed the polygon
|
||||
)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
coord_t segment_dist = (make_point(chain.back()).template cast<int64_t>() - closest.p().template cast<int64_t>()).norm();
|
||||
assert(segment_dist <= max_stitch_distance + scaled<coord_t>(0.01));
|
||||
const size_t old_size = chain.size();
|
||||
if (closest.point_idx == 0)
|
||||
{
|
||||
auto start_pos = (*closest.polygons)[closest.poly_idx].begin();
|
||||
if (segment_dist < snap_distance)
|
||||
{
|
||||
++start_pos;
|
||||
}
|
||||
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].end());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto start_pos = (*closest.polygons)[closest.poly_idx].rbegin();
|
||||
if (segment_dist < snap_distance)
|
||||
{
|
||||
++start_pos;
|
||||
}
|
||||
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].rend());
|
||||
}
|
||||
for(size_t i = old_size; i < chain.size(); ++i) //Update chain length.
|
||||
{
|
||||
chain_length += (make_point(chain[i]).template cast<int64_t>() - make_point(chain[i - 1]).template cast<int64_t>()).norm();
|
||||
}
|
||||
should_close = should_close & !isOdd((*closest.polygons)[closest.poly_idx]); //If we connect an even to an odd line, we should no longer try to close it.
|
||||
assert( ! processed[closest.poly_idx]);
|
||||
processed[closest.poly_idx] = true;
|
||||
}
|
||||
if (closest_is_closing_polygon)
|
||||
{
|
||||
if (go_in_reverse_direction)
|
||||
{ // re-reverse chain to retain original direction
|
||||
// NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction
|
||||
chain.reverse();
|
||||
}
|
||||
|
||||
break; // don't consider reverse direction
|
||||
}
|
||||
}
|
||||
if (closest_is_closing_polygon)
|
||||
{
|
||||
result_polygons.emplace_back(chain);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathsPointIndex<Paths> ppi_here(&lines, line_idx, 0);
|
||||
if ( ! canReverse(ppi_here))
|
||||
{ // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true
|
||||
// the polyline isn't allowed to be reversed, so we re-reverse it.
|
||||
chain.reverse();
|
||||
}
|
||||
result_lines.emplace_back(chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd)
|
||||
*/
|
||||
static bool canReverse(const PathsPointIndex<Paths> &polyline);
|
||||
|
||||
/*!
|
||||
* Whether two paths are allowed to be connected.
|
||||
* (Not true for an odd and an even wall.)
|
||||
*/
|
||||
static bool canConnect(const Path &a, const Path &b);
|
||||
|
||||
static bool isOdd(const Path &line);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_POLYLINE_STITCHER_H
|
||||
@@ -0,0 +1,132 @@
|
||||
//Copyright (c) 2016 Scott Lenser
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SPARSE_GRID_H
|
||||
#define UTILS_SPARSE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "SquareGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \note This is an abstract template class which doesn't have any functions to insert elements.
|
||||
* \see SparsePointGrid
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
*/
|
||||
template<class ElemT> class SparseGrid : public SquareGrid
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
using GridPoint = SquareGrid::GridPoint;
|
||||
using grid_coord_t = SquareGrid::grid_coord_t;
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem, PointHash>;
|
||||
|
||||
using iterator = typename GridMap::iterator;
|
||||
using const_iterator = typename GridMap::const_iterator;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseGrid(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f);
|
||||
|
||||
iterator begin() { return m_grid.begin(); }
|
||||
iterator end() { return m_grid.end(); }
|
||||
const_iterator begin() const { return m_grid.begin(); }
|
||||
const_iterator end() const { return m_grid.end(); }
|
||||
|
||||
/*! \brief Returns all data within radius of query_pt.
|
||||
*
|
||||
* Finds all elements with location within radius of \p query_pt. May
|
||||
* return additional elements that are beyond radius.
|
||||
*
|
||||
* Average running time is a*(1 + 2 * radius / cell_size)**2 +
|
||||
* b*cnt where a and b are proportionality constance and cnt is
|
||||
* the number of returned items. The search will return items in
|
||||
* an area of (2*radius + cell_size)**2 on average. The max range
|
||||
* of an item from the query_point is radius + cell_size.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \return Vector of elements found
|
||||
*/
|
||||
std::vector<Elem> getNearby(const Point &query_pt, coord_t radius) const;
|
||||
|
||||
/*! \brief Process elements from cells that might contain sought after points.
|
||||
*
|
||||
* Processes elements from cell that might have elements within \p
|
||||
* radius of \p query_pt. Processes all elements that are within
|
||||
* radius of query_pt. May process elements that are up to radius +
|
||||
* cell_size from query_pt.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function
|
||||
*/
|
||||
bool processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const ElemT &)> &process_func) const;
|
||||
|
||||
protected:
|
||||
/*! \brief Process elements from the cell indicated by \p grid_pt.
|
||||
*
|
||||
* \param[in] grid_pt The grid coordinates of the cell.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing a next cell.
|
||||
*/
|
||||
bool processFromCell(const GridPoint &grid_pt, const std::function<bool(const Elem &)> &process_func) const;
|
||||
|
||||
/*! \brief Map from grid locations (GridPoint) to elements (Elem). */
|
||||
GridMap m_grid;
|
||||
};
|
||||
|
||||
template<class ElemT> SparseGrid<ElemT>::SparseGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SquareGrid(cell_size)
|
||||
{
|
||||
// Must be before the reserve call.
|
||||
m_grid.max_load_factor(max_load_factor);
|
||||
if (elem_reserve != 0U)
|
||||
m_grid.reserve(elem_reserve);
|
||||
}
|
||||
|
||||
template<class ElemT> bool SparseGrid<ElemT>::processFromCell(const GridPoint &grid_pt, const std::function<bool(const Elem &)> &process_func) const
|
||||
{
|
||||
auto grid_range = m_grid.equal_range(grid_pt);
|
||||
for (auto iter = grid_range.first; iter != grid_range.second; ++iter)
|
||||
if (!process_func(iter->second))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class ElemT>
|
||||
bool SparseGrid<ElemT>::processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const Elem &)> &process_func) const
|
||||
{
|
||||
return SquareGrid::processNearby(query_pt, radius, [&process_func, this](const GridPoint &grid_pt) { return processFromCell(grid_pt, process_func); });
|
||||
}
|
||||
|
||||
template<class ElemT> std::vector<typename SparseGrid<ElemT>::Elem> SparseGrid<ElemT>::getNearby(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Elem> ret;
|
||||
const std::function<bool(const Elem &)> process_func = [&ret](const Elem &elem) {
|
||||
ret.push_back(elem);
|
||||
return true;
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_GRID_H
|
||||
@@ -0,0 +1,76 @@
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_SPARSE_LINE_GRID_H
|
||||
#define UTILS_SPARSE_LINE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "SparseGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the start and end locations from ElemT.
|
||||
* must have: std::pair<Point, Point> operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator> class SparseLineGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseLineGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
SparseLineGrid<ElemT, Locator>::SparseLineGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor)
|
||||
: SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
|
||||
|
||||
template<class ElemT, class Locator> void SparseLineGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
{
|
||||
const std::pair<Point, Point> line = m_locator(elem);
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem, PointHash>;
|
||||
// below is a workaround for the fact that lambda functions cannot access private or protected members
|
||||
// first we define a lambda which works on any GridMap and then we bind it to the actual protected GridMap of the parent class
|
||||
std::function<bool(GridMap *, const GridPoint)> process_cell_func_ = [&elem](GridMap *m_grid, const GridPoint grid_loc) {
|
||||
m_grid->emplace(grid_loc, elem);
|
||||
return true;
|
||||
};
|
||||
using namespace std::placeholders; // for _1, _2, _3...
|
||||
GridMap *m_grid = &(this->m_grid);
|
||||
std::function<bool(const GridPoint)> process_cell_func(std::bind(process_cell_func_, m_grid, _1));
|
||||
|
||||
SparseGrid<ElemT>::processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
#undef SGI_TEMPLATE
|
||||
#undef SGI_THIS
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_LINE_GRID_H
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2016 Scott Lenser
|
||||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SPARSE_POINT_GRID_H
|
||||
#define UTILS_SPARSE_POINT_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
#include "SparseGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the location from ElemT. Locator
|
||||
* must have: Point operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator> class SparsePointGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparsePointGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
SparsePointGrid<ElemT, Locator>::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
void SparsePointGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
{
|
||||
Point loc = m_locator(elem);
|
||||
GridPoint grid_loc = SparseGrid<ElemT>::toGridPoint(loc.template cast<int64_t>());
|
||||
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_POINT_GRID_H
|
||||
@@ -0,0 +1,150 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SquareGrid.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
using namespace Slic3r::Arachne;
|
||||
|
||||
|
||||
SquareGrid::SquareGrid(coord_t cell_size) : cell_size(cell_size)
|
||||
{
|
||||
assert(cell_size > 0U);
|
||||
}
|
||||
|
||||
|
||||
SquareGrid::GridPoint SquareGrid::toGridPoint(const Vec2i64 &point) const
|
||||
{
|
||||
return Point(toGridCoord(point.x()), toGridCoord(point.y()));
|
||||
}
|
||||
|
||||
|
||||
SquareGrid::grid_coord_t SquareGrid::toGridCoord(const int64_t &coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return coord / cell_size;
|
||||
}
|
||||
|
||||
coord_t SquareGrid::toLowerCoord(const grid_coord_t& grid_coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return grid_coord * cell_size;
|
||||
}
|
||||
|
||||
|
||||
bool SquareGrid::processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func)
|
||||
{
|
||||
return static_cast<const SquareGrid*>(this)->processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
|
||||
bool SquareGrid::processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func) const
|
||||
{
|
||||
Point start = line.first;
|
||||
Point end = line.second;
|
||||
if (end.x() < start.x())
|
||||
{ // make sure X increases between start and end
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
const GridPoint start_cell = toGridPoint(start.cast<int64_t>());
|
||||
const GridPoint end_cell = toGridPoint(end.cast<int64_t>());
|
||||
const int64_t y_diff = int64_t(end.y() - start.y());
|
||||
const grid_coord_t y_dir = nonzeroSign(y_diff);
|
||||
|
||||
/* This line drawing algorithm iterates over the range of Y coordinates, and
|
||||
for each Y coordinate computes the range of X coordinates crossed in one
|
||||
unit of Y. These ranges are rounded to be inclusive, so effectively this
|
||||
creates a "fat" line, marking more cells than a strict one-cell-wide path.*/
|
||||
grid_coord_t x_cell_start = start_cell.x();
|
||||
for (grid_coord_t cell_y = start_cell.y(); cell_y * y_dir <= end_cell.y() * y_dir; cell_y += y_dir)
|
||||
{ // for all Y from start to end
|
||||
// nearest y coordinate of the cells in the next row
|
||||
const coord_t nearest_next_y = toLowerCoord(cell_y + ((nonzeroSign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0)));
|
||||
grid_coord_t x_cell_end; // the X coord of the last cell to include from this row
|
||||
if (y_diff == 0)
|
||||
{
|
||||
x_cell_end = end_cell.x();
|
||||
}
|
||||
else
|
||||
{
|
||||
const int64_t area = int64_t(end.x() - start.x()) * int64_t(nearest_next_y - start.y());
|
||||
// corresponding_x: the x coordinate corresponding to nearest_next_y
|
||||
int64_t corresponding_x = int64_t(start.x()) + area / y_diff;
|
||||
x_cell_end = toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0)));
|
||||
if (x_cell_end < start_cell.x())
|
||||
{ // process at least one cell!
|
||||
x_cell_end = x_cell_start;
|
||||
}
|
||||
}
|
||||
|
||||
for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x)
|
||||
{
|
||||
GridPoint grid_loc(cell_x, cell_y);
|
||||
if (! process_cell_func(grid_loc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (grid_loc == end_cell)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO: this causes at least a one cell overlap for each row, which
|
||||
// includes extra cells when crossing precisely on the corners
|
||||
// where positive slope where x > 0 and negative slope where x < 0
|
||||
x_cell_start = x_cell_end;
|
||||
}
|
||||
assert(false && "We should have returned already before here!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SquareGrid::processNearby
|
||||
(
|
||||
const Point &query_pt,
|
||||
coord_t radius,
|
||||
const std::function<bool (const GridPoint&)>& process_func
|
||||
) const
|
||||
{
|
||||
const Point min_loc(query_pt.x() - radius, query_pt.y() - radius);
|
||||
const Point max_loc(query_pt.x() + radius, query_pt.y() + radius);
|
||||
|
||||
GridPoint min_grid = toGridPoint(min_loc.cast<int64_t>());
|
||||
GridPoint max_grid = toGridPoint(max_loc.cast<int64_t>());
|
||||
|
||||
for (coord_t grid_y = min_grid.y(); grid_y <= max_grid.y(); ++grid_y)
|
||||
{
|
||||
for (coord_t grid_x = min_grid.x(); grid_x <= max_grid.x(); ++grid_x)
|
||||
{
|
||||
GridPoint grid_pt(grid_x,grid_y);
|
||||
if (!process_func(grid_pt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SquareGrid::grid_coord_t SquareGrid::nonzeroSign(const grid_coord_t z) const
|
||||
{
|
||||
return (z >= 0) - (z < 0);
|
||||
}
|
||||
|
||||
coord_t SquareGrid::getCellSize() const
|
||||
{
|
||||
return cell_size;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SQUARE_GRID_H
|
||||
#define UTILS_SQUARE_GRID_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*!
|
||||
* Helper class to calculate coordinates on a square grid, and providing some
|
||||
* utility functions to process grids.
|
||||
*
|
||||
* Doesn't contain any data, except cell size. The purpose is only to
|
||||
* automatically generate coordinates on a grid, and automatically feed them to
|
||||
* functions.
|
||||
* The grid is theoretically infinite (bar integer limits).
|
||||
*/
|
||||
class SquareGrid
|
||||
{
|
||||
public:
|
||||
/*! \brief Constructs a grid with the specified cell size.
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
*/
|
||||
SquareGrid(const coord_t cell_size);
|
||||
|
||||
/*!
|
||||
* Get the cell size this grid was created for.
|
||||
*/
|
||||
coord_t getCellSize() const;
|
||||
|
||||
using GridPoint = Point;
|
||||
using grid_coord_t = coord_t;
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param line The line along which to process cells.
|
||||
* \param process_func Processes each cell. ``process_func(elem)`` is called
|
||||
* for each cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func);
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param line The line along which to process cells
|
||||
* \param process_func Processes each cell. ``process_func(elem)`` is called
|
||||
* for each cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func) const;
|
||||
|
||||
/*! \brief Process cells that might contain sought after points.
|
||||
*
|
||||
* Processes cells that might be within a square with twice \p radius as
|
||||
* width, centered around \p query_pt.
|
||||
* May process elements that are up to radius + cell_size from query_pt.
|
||||
* \param query_pt The point to search around.
|
||||
* \param radius The search radius.
|
||||
* \param process_func Processes each cell. ``process_func(loc)`` is called
|
||||
* for each cell coord within range. Processing stops if function returns
|
||||
* ``false``.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const GridPoint &)> &process_func) const;
|
||||
|
||||
/*! \brief Compute the grid coordinates of a point.
|
||||
* \param point The actual location.
|
||||
* \return The grid coordinates that correspond to \p point.
|
||||
*/
|
||||
GridPoint toGridPoint(const Vec2i64 &point) const;
|
||||
|
||||
/*! \brief Compute the grid coordinate of a real space coordinate.
|
||||
* \param coord The actual location.
|
||||
* \return The grid coordinate that corresponds to \p coord.
|
||||
*/
|
||||
grid_coord_t toGridCoord(const int64_t &coord) const;
|
||||
|
||||
/*! \brief Compute the lowest coord in a grid cell.
|
||||
* The lowest point is the point in the grid cell closest to the origin.
|
||||
*
|
||||
* \param grid_coord The grid coordinate.
|
||||
* \return The print space coordinate that corresponds to \p grid_coord.
|
||||
*/
|
||||
coord_t toLowerCoord(const grid_coord_t &grid_coord) const;
|
||||
|
||||
protected:
|
||||
/*! \brief The cell (square) size. */
|
||||
coord_t cell_size;
|
||||
|
||||
/*!
|
||||
* Compute the sign of a number.
|
||||
*
|
||||
* The number 0 will result in a positive sign (1).
|
||||
* \param z The number to find the sign of.
|
||||
* \return 1 if the number is positive or 0, or -1 if the number is
|
||||
* negative.
|
||||
*/
|
||||
grid_coord_t nonzeroSign(grid_coord_t z) const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif //UTILS_SQUARE_GRID_H
|
||||
@@ -0,0 +1,65 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_LINEAR_ALG_2D_H
|
||||
#define UTILS_LINEAR_ALG_2D_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne::LinearAlg2D
|
||||
{
|
||||
|
||||
/*!
|
||||
* Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows.
|
||||
*
|
||||
* The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b
|
||||
* The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b.
|
||||
*
|
||||
* \param p the point to check
|
||||
* \param a the from point of the line
|
||||
* \param b the to point of the line
|
||||
* \return a positive value when \p p lies to the left of the line from \p a to \p b
|
||||
*/
|
||||
static inline int64_t pointIsLeftOfLine(const Point &p, const Point &a, const Point &b)
|
||||
{
|
||||
return int64_t(b.x() - a.x()) * int64_t(p.y() - a.y()) - int64_t(b.y() - a.y()) * int64_t(p.x() - a.x());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the angle between two consecutive line segments.
|
||||
*
|
||||
* The angle is computed from the left side of b when looking from a.
|
||||
*
|
||||
* c
|
||||
* \ .
|
||||
* \ b
|
||||
* angle|
|
||||
* |
|
||||
* a
|
||||
*
|
||||
* \param a start of first line segment
|
||||
* \param b end of first segment and start of second line segment
|
||||
* \param c end of second line segment
|
||||
* \return the angle in radians between 0 and 2 * pi of the corner in \p b
|
||||
*/
|
||||
static inline float getAngleLeft(const Point &a, const Point &b, const Point &c)
|
||||
{
|
||||
const Vec2i64 ba = (a - b).cast<int64_t>();
|
||||
const Vec2i64 bc = (c - b).cast<int64_t>();
|
||||
const int64_t dott = ba.dot(bc); // dot product
|
||||
const int64_t det = cross2(ba, bc); // determinant
|
||||
if (det == 0) {
|
||||
if ((ba.x() != 0 && (ba.x() > 0) == (bc.x() > 0)) || (ba.x() == 0 && (ba.y() > 0) == (bc.y() > 0)))
|
||||
return 0; // pointy bit
|
||||
else
|
||||
return float(M_PI); // straight bit
|
||||
}
|
||||
const float angle = -atan2(double(det), double(dott)); // from -pi to pi
|
||||
if (angle >= 0)
|
||||
return angle;
|
||||
else
|
||||
return M_PI * 2 + angle;
|
||||
}
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
#endif//UTILS_LINEAR_ALG_2D_H
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "ArcFitter.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ArcFitter::do_arc_fitting(const Points& points, std::vector<PathFittingData>& result, double tolerance)
|
||||
{
|
||||
#ifdef DEBUG_ARC_FITTING
|
||||
static int irun = 0;
|
||||
BoundingBox bbox_svg;
|
||||
bbox_svg.merge(get_extents(points));
|
||||
Polyline temp = Polyline(points);
|
||||
{
|
||||
std::stringstream stri;
|
||||
stri << "debug_arc_fitting_" << irun << ".svg";
|
||||
SVG svg(stri.str(), bbox_svg);
|
||||
svg.draw(points, "blue", 50000);
|
||||
svg.draw(temp, "red", 1);
|
||||
svg.Close();
|
||||
}
|
||||
++ irun;
|
||||
#endif
|
||||
|
||||
result.clear();
|
||||
result.reserve(points.size() / 2); //worst case size
|
||||
if (points.size() < 3) {
|
||||
PathFittingData data;
|
||||
data.start_point_index = 0;
|
||||
data.end_point_index = points.size() - 1;
|
||||
data.path_type = EMovePathType::Linear_move;
|
||||
result.push_back(data);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t front_index = 0;
|
||||
size_t back_index = 0;
|
||||
ArcSegment last_arc;
|
||||
bool can_fit = false;
|
||||
Points current_segment;
|
||||
current_segment.reserve(points.size());
|
||||
ArcSegment target_arc;
|
||||
for (size_t i = 0; i < points.size(); i++) {
|
||||
//BBS: point in stack is not enough, build stack first
|
||||
back_index = i;
|
||||
current_segment.push_back(points[i]);
|
||||
if (back_index - front_index < 2)
|
||||
continue;
|
||||
|
||||
can_fit = ArcSegment::try_create_arc(current_segment, target_arc, Polyline(current_segment).length(),
|
||||
DEFAULT_SCALED_MAX_RADIUS,
|
||||
tolerance,
|
||||
DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
|
||||
if (can_fit) {
|
||||
//BBS: can be fit as arc, then save arc data temperarily
|
||||
last_arc = target_arc;
|
||||
if (back_index == points.size() - 1) {
|
||||
result.emplace_back(std::move(PathFittingData{ front_index,
|
||||
back_index,
|
||||
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
|
||||
last_arc }));
|
||||
front_index = back_index;
|
||||
}
|
||||
} else {
|
||||
if (back_index - front_index > 2) {
|
||||
//BBS: althought current point_stack can't be fit as arc,
|
||||
//but previous must can be fit if removing the top in stack, so save last arc
|
||||
result.emplace_back(std::move(PathFittingData{ front_index,
|
||||
back_index - 1,
|
||||
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
|
||||
last_arc }));
|
||||
} else {
|
||||
//BBS: save the first segment as line move when 3 point-line can't be fit as arc move
|
||||
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
|
||||
result.emplace_back(std::move(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()}));
|
||||
else if(result.back().path_type == EMovePathType::Linear_move)
|
||||
result.back().end_point_index = front_index + 1;
|
||||
}
|
||||
front_index = back_index - 1;
|
||||
current_segment.clear();
|
||||
current_segment.push_back(points[front_index]);
|
||||
current_segment.push_back(points[front_index + 1]);
|
||||
}
|
||||
}
|
||||
//BBS: handle the remain data
|
||||
if (front_index != back_index) {
|
||||
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
|
||||
result.emplace_back(std::move(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()}));
|
||||
else if (result.back().path_type == EMovePathType::Linear_move)
|
||||
result.back().end_point_index = back_index;
|
||||
}
|
||||
result.shrink_to_fit();
|
||||
}
|
||||
|
||||
void ArcFitter::do_arc_fitting_and_simplify(Points& points, std::vector<PathFittingData>& result, double tolerance)
|
||||
{
|
||||
//BBS: 1 do arc fit first
|
||||
if (abs(tolerance) > SCALED_EPSILON)
|
||||
ArcFitter::do_arc_fitting(points, result, tolerance);
|
||||
else
|
||||
result.push_back(PathFittingData{ 0, points.size() - 1, EMovePathType::Linear_move, ArcSegment() });
|
||||
|
||||
//BBS: 2 for straight part which can't fit arc, use DP simplify
|
||||
//for arc part, only need to keep start and end point
|
||||
if (result.size() == 1 && result[0].path_type == EMovePathType::Linear_move) {
|
||||
//BBS: all are straight segment, directly use DP simplify
|
||||
points = MultiPoint::_douglas_peucker(points, tolerance);
|
||||
result[0].end_point_index = points.size() - 1;
|
||||
return;
|
||||
} else {
|
||||
//BBS: has both arc part and straight part, we should spilit the straight part out and do DP simplify
|
||||
Points simplified_points;
|
||||
simplified_points.reserve(points.size());
|
||||
simplified_points.push_back(points[0]);
|
||||
std::vector<size_t> reduce_count(result.size(), 0);
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
{
|
||||
size_t start_index = result[i].start_point_index;
|
||||
size_t end_index = result[i].end_point_index;
|
||||
//BBS: get the straight and arc part, and do simplifing independently.
|
||||
//Why: It's obvious that we need to use DP to simplify straight part to reduce point.
|
||||
//For arc part, theoretically, we only need to keep the start and end point, and
|
||||
//delete all other point. But when considering wipe operation, we must keep the original
|
||||
//point data and shouldn't reduce too much by only saving start and end point.
|
||||
Points straight_or_arc_part;
|
||||
straight_or_arc_part.reserve(end_index - start_index + 1);
|
||||
for (size_t j = start_index; j <= end_index; j++)
|
||||
straight_or_arc_part.push_back(points[j]);
|
||||
straight_or_arc_part = MultiPoint::_douglas_peucker(straight_or_arc_part, tolerance);
|
||||
//BBS: how many point has been reduced
|
||||
reduce_count[i] = end_index - start_index + 1 - straight_or_arc_part.size();
|
||||
//BBS: save the simplified result
|
||||
for (size_t j = 1; j < straight_or_arc_part.size(); j++) {
|
||||
simplified_points.push_back(straight_or_arc_part[j]);
|
||||
}
|
||||
}
|
||||
//BBS: save and will return the simplified_points
|
||||
points = simplified_points;
|
||||
//BBS: modify the index in result because the point index must be changed to match the simplified points
|
||||
for (size_t j = 1; j < reduce_count.size(); j++)
|
||||
reduce_count[j] += reduce_count[j - 1];
|
||||
for (size_t j = 0; j < result.size(); j++)
|
||||
{
|
||||
result[j].end_point_index -= reduce_count[j];
|
||||
if (j != result.size() - 1)
|
||||
result[j + 1].start_point_index = result[j].end_point_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef slic3r_ArcFitter_hpp_
|
||||
#define slic3r_ArcFitter_hpp_
|
||||
|
||||
#include "Circle.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//BBS: linear move(G0 and G1) or arc move(G2 and G3).
|
||||
enum class EMovePathType : unsigned char
|
||||
{
|
||||
Noop_move,
|
||||
Linear_move,
|
||||
Arc_move_cw,
|
||||
Arc_move_ccw,
|
||||
Count
|
||||
};
|
||||
|
||||
//BBS
|
||||
struct PathFittingData{
|
||||
size_t start_point_index;
|
||||
size_t end_point_index;
|
||||
EMovePathType path_type;
|
||||
// BBS: only valid when path_type is arc move
|
||||
// Used to store detail information of arc segment
|
||||
ArcSegment arc_data;
|
||||
|
||||
bool is_linear_move() {
|
||||
return (path_type == EMovePathType::Linear_move);
|
||||
}
|
||||
bool is_arc_move() {
|
||||
return (path_type == EMovePathType::Arc_move_ccw || path_type == EMovePathType::Arc_move_cw);
|
||||
}
|
||||
bool reverse_arc_path() {
|
||||
if (!is_arc_move() || !arc_data.reverse())
|
||||
return false;
|
||||
path_type = (arc_data.direction == ArcDirection::Arc_Dir_CCW) ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ArcFitter {
|
||||
public:
|
||||
//BBS: this function is used to check the point list and return which part can fit as arc, which part should be line
|
||||
static void do_arc_fitting(const Points& points, std::vector<PathFittingData> &result, double tolerance);
|
||||
//BBS: this function is used to check the point list and return which part can fit as arc, which part should be line.
|
||||
//By the way, it also use DP simplify to reduce point of straight part and only keep the start and end point of arc.
|
||||
static void do_arc_fitting_and_simplify(Points& points, std::vector<PathFittingData>& result, double tolerance);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,219 @@
|
||||
#ifndef ARRANGE_HPP
|
||||
#define ARRANGE_HPP
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Print.hpp"
|
||||
|
||||
#define BED_SHRINK_SEQ_PRINT 5
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBox;
|
||||
|
||||
namespace arrangement {
|
||||
|
||||
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
||||
class CircleBed {
|
||||
Point center_;
|
||||
double radius_;
|
||||
public:
|
||||
|
||||
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
|
||||
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||
|
||||
inline double radius() const { return radius_; }
|
||||
inline const Point& center() const { return center_; }
|
||||
};
|
||||
|
||||
/// Representing an unbounded bed.
|
||||
struct InfiniteBed {
|
||||
Point center;
|
||||
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||
};
|
||||
|
||||
/// A logical bed representing an object not being arranged. Either the arrange
|
||||
/// has not yet successfully run on this ArrangePolygon or it could not fit the
|
||||
/// object due to overly large size or invalid geometry.
|
||||
static const constexpr int UNARRANGED = -1;
|
||||
|
||||
/// Input/Output structure for the arrange() function. The poly field will not
|
||||
/// be modified during arrangement. Instead, the translation and rotation fields
|
||||
/// will mark the needed transformation for the polygon to be in the arranged
|
||||
/// position. These can also be set to an initial offset and rotation.
|
||||
///
|
||||
/// The bed_idx field will indicate the logical bed into which the
|
||||
/// polygon belongs: UNARRANGED means no place for the polygon
|
||||
/// (also the initial state before arrange), 0..N means the index of the bed.
|
||||
/// Zero is the physical bed, larger than zero means a virtual bed.
|
||||
struct ArrangePolygon {
|
||||
ExPolygon poly; /// The 2D silhouette to be arranged
|
||||
Vec2crd translation{0, 0}; /// The translation of the poly
|
||||
double rotation{0.0}; /// The rotation of the poly in radians
|
||||
coord_t inflation = 0; /// Arrange with inflated polygon
|
||||
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
||||
int priority{0};
|
||||
//BBS: add locked_plate to indicate whether it is in the locked plate
|
||||
int locked_plate{ -1 };
|
||||
bool is_virt_object{ false };
|
||||
bool is_extrusion_cali_object{ false };
|
||||
bool is_wipe_tower{ false };
|
||||
bool has_tree_support{false};
|
||||
//BBS: add row/col for sudoku-style layout
|
||||
int row{0};
|
||||
int col{0};
|
||||
std::vector<int> extrude_ids{}; /// extruder_id for least extruder switch
|
||||
int filament_temp_type{ -1 };
|
||||
int bed_temp{0}; ///bed temperature for different material judge
|
||||
int print_temp{0}; ///print temperature for different material judge
|
||||
int first_bed_temp{ 0 }; ///first layer bed temperature for different material judge
|
||||
int first_print_temp{ 0 }; ///first layer print temperature for different material judge
|
||||
int vitrify_temp{ 0 }; // max bed temperature for material compatibility, which is usually the filament vitrification temp
|
||||
int itemid{ 0 }; // item id in the vector, used for accessing all possible params like extrude_id
|
||||
int is_applied{ 0 }; // transform has been applied
|
||||
double height{ 0 }; // item height
|
||||
double brim_width{ 0 }; // brim width
|
||||
std::string name;
|
||||
|
||||
// If empty, any rotation is allowed (currently unsupported)
|
||||
// If only a zero is there, no rotation is allowed
|
||||
std::vector<double> allowed_rotations = {0.};
|
||||
|
||||
/// Optional setter function which can store arbitrary data in its closure
|
||||
std::function<void(const ArrangePolygon&)> setter = nullptr;
|
||||
|
||||
/// Helper function to call the setter with the arrange data arguments
|
||||
void apply() {
|
||||
if (setter && !is_applied) {
|
||||
setter(*this);
|
||||
is_applied = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if arrange() was called previously and gave a successful result.
|
||||
bool is_arranged() const { return bed_idx != UNARRANGED; }
|
||||
|
||||
inline ExPolygon transformed_poly() const
|
||||
{
|
||||
ExPolygon ret = poly;
|
||||
ret.rotate(rotation);
|
||||
ret.translate(translation.x(), translation.y());
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
||||
|
||||
struct ArrangeParams {
|
||||
|
||||
/// The minimum distance which is allowed for any
|
||||
/// pair of items on the print bed in any direction.
|
||||
coord_t min_obj_distance = 0;
|
||||
|
||||
/// The accuracy of optimization.
|
||||
/// Goes from 0.0 to 1.0 and scales performance as well
|
||||
float accuracy = 1.f;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
bool allow_rotations = false;
|
||||
|
||||
bool do_final_align = true;
|
||||
|
||||
//BBS: add specific arrange params
|
||||
bool allow_multi_materials_on_same_plate = true;
|
||||
bool avoid_extrusion_cali_region = true;
|
||||
bool is_seq_print = false;
|
||||
bool align_to_y_axis = false;
|
||||
float bed_shrink_x = 1;
|
||||
float bed_shrink_y = 1;
|
||||
float brim_skirt_distance = 0;
|
||||
float clearance_height_to_rod = 0;
|
||||
float clearance_height_to_lid = 0;
|
||||
float clearance_radius = 0;
|
||||
float object_skirt_offset = 0;
|
||||
float nozzle_height = 0;
|
||||
float printable_height = 256.0;
|
||||
Vec2d align_center{ 0.5,0.5 };
|
||||
|
||||
ArrangePolygons excluded_regions; // regions cant't be used
|
||||
ArrangePolygons nonprefered_regions; // regions can be used but not prefered
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = [](unsigned st, std::string str = "") {
|
||||
std::cout << "st=" << st << ", " << str << std::endl;
|
||||
};
|
||||
|
||||
std::function<void(const ArrangePolygon &)> on_packed;
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition;
|
||||
|
||||
ArrangeParams() = default;
|
||||
explicit ArrangeParams(coord_t md) : min_obj_distance(md) {}
|
||||
// to json format
|
||||
std::string to_json() const{
|
||||
std::string ret = "{";
|
||||
ret += "\"min_obj_distance\":" + std::to_string(min_obj_distance) + ",";
|
||||
ret += "\"accuracy\":" + std::to_string(accuracy) + ",";
|
||||
ret += "\"parallel\":" + std::to_string(parallel) + ",";
|
||||
ret += "\"allow_rotations\":" + std::to_string(allow_rotations) + ",";
|
||||
ret += "\"do_final_align\":" + std::to_string(do_final_align) + ",";
|
||||
ret += "\"allow_multi_materials_on_same_plate\":" + std::to_string(allow_multi_materials_on_same_plate) + ",";
|
||||
ret += "\"avoid_extrusion_cali_region\":" + std::to_string(avoid_extrusion_cali_region) + ",";
|
||||
ret += "\"is_seq_print\":" + std::to_string(is_seq_print) + ",";
|
||||
ret += "\"bed_shrink_x\":" + std::to_string(bed_shrink_x) + ",";
|
||||
ret += "\"bed_shrink_y\":" + std::to_string(bed_shrink_y) + ",";
|
||||
ret += "\"brim_skirt_distance\":" + std::to_string(brim_skirt_distance) + ",";
|
||||
ret += "\"clearance_height_to_rod\":" + std::to_string(clearance_height_to_rod) + ",";
|
||||
ret += "\"clearance_height_to_lid\":" + std::to_string(clearance_height_to_lid) + ",";
|
||||
ret += "\"clearance_radius\":" + std::to_string(clearance_radius) + ",";
|
||||
ret += "\"printable_height\":" + std::to_string(printable_height) + ",";
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig* print_cfg, const ArrangePolygons& selected);
|
||||
|
||||
void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, ArrangeParams& params);
|
||||
|
||||
void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params);
|
||||
|
||||
void update_selected_items_axis_align(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params);
|
||||
|
||||
Points get_shrink_bedpts(const DynamicPrintConfig* print_cfg, const ArrangeParams& params);
|
||||
|
||||
/**
|
||||
* \brief Arranges the input polygons.
|
||||
*
|
||||
* WARNING: Currently, only convex polygons are supported by the libnest2d
|
||||
* library which is used to do the arrangement. This might change in the future
|
||||
* this is why the interface contains a general polygon capable to have holes.
|
||||
*
|
||||
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||
* and bin_idx fields will be changed after the call finished and can be used
|
||||
* to apply the result on the input polygon.
|
||||
*/
|
||||
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {});
|
||||
|
||||
// A dispatch function that determines the bed shape from a set of points.
|
||||
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms);
|
||||
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
|
||||
}} // namespace Slic3r::arrangement
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "BlacklistedLibraryCheck.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <psapi.h>
|
||||
# endif //WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
//only dll name with .dll suffix - currently case sensitive
|
||||
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll", L"amhook.dll", L"AMHook.dll" });
|
||||
|
||||
bool BlacklistedLibraryCheck::get_blacklisted(std::vector<std::wstring>& names)
|
||||
{
|
||||
if (m_found.empty())
|
||||
return false;
|
||||
for (const auto& lib : m_found)
|
||||
names.emplace_back(lib);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring BlacklistedLibraryCheck::get_blacklisted_string()
|
||||
{
|
||||
std::wstring ret;
|
||||
for (const auto& lib : m_found)
|
||||
ret += lib + L"\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BlacklistedLibraryCheck::perform_check()
|
||||
{
|
||||
// Get the pseudo-handle for the current process.
|
||||
HANDLE hCurrentProcess = GetCurrentProcess();
|
||||
|
||||
// Get a list of all the modules in this process.
|
||||
HMODULE hMods[1024];
|
||||
DWORD cbNeeded;
|
||||
if (EnumProcessModulesEx(hCurrentProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
|
||||
{
|
||||
//printf("Total Dlls: %d\n", cbNeeded / sizeof(HMODULE));
|
||||
for (unsigned int i = 0; i < cbNeeded / sizeof(HMODULE); ++ i)
|
||||
{
|
||||
wchar_t szModName[MAX_PATH];
|
||||
// Get the full path to the module's file.
|
||||
if (GetModuleFileNameExW(hCurrentProcess, hMods[i], szModName, MAX_PATH))
|
||||
{
|
||||
// Add to list if blacklisted
|
||||
if (BlacklistedLibraryCheck::is_blacklisted(szModName)) {
|
||||
//wprintf(L"Contains library: %s\n", szModName);
|
||||
if (std::find(m_found.begin(), m_found.end(), szModName) == m_found.end())
|
||||
m_found.emplace_back(szModName);
|
||||
}
|
||||
//wprintf(L"%s\n", szModName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//printf("\n");
|
||||
return !m_found.empty();
|
||||
}
|
||||
|
||||
bool BlacklistedLibraryCheck::is_blacklisted(const std::wstring &dllpath)
|
||||
{
|
||||
std::wstring dllname = boost::filesystem::path(dllpath).filename().wstring();
|
||||
//std::transform(dllname.begin(), dllname.end(), dllname.begin(), std::tolower);
|
||||
if (std::find(BlacklistedLibraryCheck::blacklist.begin(), BlacklistedLibraryCheck::blacklist.end(), dllname) != BlacklistedLibraryCheck::blacklist.end()) {
|
||||
//std::wprintf(L"%s is blacklisted\n", dllname.c_str());
|
||||
return true;
|
||||
}
|
||||
//std::wprintf(L"%s is NOT blacklisted\n", dllname.c_str());
|
||||
return false;
|
||||
}
|
||||
bool BlacklistedLibraryCheck::is_blacklisted(const std::string &dllpath)
|
||||
{
|
||||
return BlacklistedLibraryCheck::is_blacklisted(boost::nowide::widen(dllpath));
|
||||
}
|
||||
|
||||
#endif //WIN32
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef slic3r_BlacklistedLibraryCheck_hpp_
|
||||
#define slic3r_BlacklistedLibraryCheck_hpp_
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#endif //WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
class BlacklistedLibraryCheck
|
||||
{
|
||||
public:
|
||||
static BlacklistedLibraryCheck& get_instance()
|
||||
{
|
||||
static BlacklistedLibraryCheck instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
private:
|
||||
BlacklistedLibraryCheck() = default;
|
||||
|
||||
std::vector<std::wstring> m_found;
|
||||
public:
|
||||
BlacklistedLibraryCheck(BlacklistedLibraryCheck const&) = delete;
|
||||
void operator=(BlacklistedLibraryCheck const&) = delete;
|
||||
// returns all found blacklisted dlls
|
||||
bool get_blacklisted(std::vector<std::wstring>& names);
|
||||
std::wstring get_blacklisted_string();
|
||||
// returns true if enumerating found blacklisted dll
|
||||
bool perform_check();
|
||||
|
||||
// UTF-8 encoded path
|
||||
static bool is_blacklisted(const std::string &dllpath);
|
||||
static bool is_blacklisted(const std::wstring &dllpath);
|
||||
private:
|
||||
static const std::vector<std::wstring> blacklist;
|
||||
};
|
||||
|
||||
#endif //WIN32
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif //slic3r_BlacklistedLibraryCheck_hpp_
|
||||
@@ -0,0 +1,287 @@
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template BoundingBoxBase<Point, Points>::BoundingBoxBase(const Points &points);
|
||||
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
|
||||
|
||||
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
|
||||
|
||||
void BoundingBox::polygon(Polygon* polygon) const
|
||||
{
|
||||
polygon->points = {
|
||||
this->min,
|
||||
{ this->max.x(), this->min.y() },
|
||||
this->max,
|
||||
{ this->min.x(), this->max.y() }
|
||||
};
|
||||
}
|
||||
|
||||
Polygon BoundingBox::polygon() const
|
||||
{
|
||||
Polygon p;
|
||||
this->polygon(&p);
|
||||
return p;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::rotated(double angle) const
|
||||
{
|
||||
BoundingBox out;
|
||||
out.merge(this->min.rotated(angle));
|
||||
out.merge(this->max.rotated(angle));
|
||||
out.merge(Point(this->min.x(), this->max.y()).rotated(angle));
|
||||
out.merge(Point(this->max.x(), this->min.y()).rotated(angle));
|
||||
return out;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const
|
||||
{
|
||||
BoundingBox out;
|
||||
out.merge(this->min.rotated(angle, center));
|
||||
out.merge(this->max.rotated(angle, center));
|
||||
out.merge(Point(this->min.x(), this->max.y()).rotated(angle, center));
|
||||
out.merge(Point(this->max.x(), this->min.y()).rotated(angle, center));
|
||||
return out;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::scaled(double factor) const
|
||||
{
|
||||
BoundingBox out(*this);
|
||||
out.scale(factor);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class PointType, typename APointsType> void
|
||||
BoundingBoxBase<PointType, APointsType>::scale(double factor)
|
||||
{
|
||||
this->min *= factor;
|
||||
this->max *= factor;
|
||||
}
|
||||
template void BoundingBoxBase<Point, Points>::scale(double factor);
|
||||
template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
|
||||
template <class PointType, typename APointsType> void
|
||||
BoundingBoxBase<PointType, APointsType>::merge(const PointType &point)
|
||||
{
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(point);
|
||||
this->max = this->max.cwiseMax(point);
|
||||
} else {
|
||||
this->min = point;
|
||||
this->max = point;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point, Points>::merge(const Point &point);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
|
||||
template <class PointType, typename APointsType> void
|
||||
BoundingBoxBase<PointType, APointsType>::merge(const PointsType &points)
|
||||
{
|
||||
this->merge(BoundingBoxBase(points));
|
||||
}
|
||||
template void BoundingBoxBase<Point, Points>::merge(const Points &points);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
|
||||
template <class PointType, typename APointsType> void
|
||||
BoundingBoxBase<PointType, APointsType>::merge(const BoundingBoxBase<PointType, PointsType> &bb)
|
||||
{
|
||||
assert(bb.defined || bb.min.x() >= bb.max.x() || bb.min.y() >= bb.max.y());
|
||||
if (bb.defined) {
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(bb.min);
|
||||
this->max = this->max.cwiseMax(bb.max);
|
||||
} else {
|
||||
this->min = bb.min;
|
||||
this->max = bb.max;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point, Points>::merge(const BoundingBoxBase<Point, Points> &bb);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
|
||||
//BBS
|
||||
template <class PointType>
|
||||
Polygon BoundingBox3Base<PointType>::polygon(bool is_scaled) const
|
||||
{
|
||||
Polygon polygon;
|
||||
polygon.points.clear();
|
||||
polygon.points.resize(4);
|
||||
double scale_factor = 1 / (is_scaled ? SCALING_FACTOR : 1);
|
||||
polygon.points[0](0) = this->min(0) * scale_factor;
|
||||
polygon.points[0](1) = this->min(1) * scale_factor;
|
||||
polygon.points[1](0) = this->max(0) * scale_factor;
|
||||
polygon.points[1](1) = this->min(1) * scale_factor;
|
||||
polygon.points[2](0) = this->max(0) * scale_factor;
|
||||
polygon.points[2](1) = this->max(1) * scale_factor;
|
||||
polygon.points[3](0) = this->min(0) * scale_factor;
|
||||
polygon.points[3](1) = this->max(1) * scale_factor;
|
||||
return polygon;
|
||||
}
|
||||
template Polygon BoundingBox3Base<Vec3f>::polygon(bool is_scaled) const;
|
||||
template Polygon BoundingBox3Base<Vec3d>::polygon(bool is_scaled) const;
|
||||
|
||||
template <class PointType> void
|
||||
BoundingBox3Base<PointType>::merge(const PointType &point)
|
||||
{
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(point);
|
||||
this->max = this->max.cwiseMax(point);
|
||||
} else {
|
||||
this->min = point;
|
||||
this->max = point;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
|
||||
template <class PointType> void
|
||||
BoundingBox3Base<PointType>::merge(const PointsType &points)
|
||||
{
|
||||
this->merge(BoundingBox3Base(points));
|
||||
}
|
||||
template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||
|
||||
template <class PointType> void
|
||||
BoundingBox3Base<PointType>::merge(const BoundingBox3Base<PointType> &bb)
|
||||
{
|
||||
assert(bb.defined || bb.min.x() >= bb.max.x() || bb.min.y() >= bb.max.y() || bb.min.z() >= bb.max.z());
|
||||
if (bb.defined) {
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(bb.min);
|
||||
this->max = this->max.cwiseMax(bb.max);
|
||||
} else {
|
||||
this->min = bb.min;
|
||||
this->max = bb.max;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||
|
||||
template <class PointType, typename APointsType> PointType
|
||||
BoundingBoxBase<PointType, APointsType>::size() const
|
||||
{
|
||||
return this->max - this->min;
|
||||
}
|
||||
template Point BoundingBoxBase<Point, Points>::size() const;
|
||||
template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
|
||||
template <class PointType> PointType
|
||||
BoundingBox3Base<PointType>::size() const
|
||||
{
|
||||
return this->max - this->min;
|
||||
}
|
||||
template Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
|
||||
template <class PointType, typename APointsType> double BoundingBoxBase<PointType, APointsType>::radius() const
|
||||
{
|
||||
assert(this->defined);
|
||||
return 0.5 * (this->max - this->min).template cast<double>().norm();
|
||||
}
|
||||
template double BoundingBoxBase<Point, Points>::radius() const;
|
||||
template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
|
||||
template <class PointType> double BoundingBox3Base<PointType>::radius() const
|
||||
{
|
||||
return 0.5 * (this->max - this->min).template cast<double>().norm();
|
||||
}
|
||||
template double BoundingBox3Base<Vec3d>::radius() const;
|
||||
|
||||
template <class PointType, typename APointsType> void
|
||||
BoundingBoxBase<PointType, APointsType>::offset(coordf_t delta)
|
||||
{
|
||||
PointType v(delta, delta);
|
||||
this->min -= v;
|
||||
this->max += v;
|
||||
}
|
||||
template void BoundingBoxBase<Point, Points>::offset(coordf_t delta);
|
||||
template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
|
||||
template <class PointType> void
|
||||
BoundingBox3Base<PointType>::offset(coordf_t delta)
|
||||
{
|
||||
PointType v(delta, delta, delta);
|
||||
this->min -= v;
|
||||
this->max += v;
|
||||
}
|
||||
template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||
|
||||
template <class PointType, typename APointsType> PointType
|
||||
BoundingBoxBase<PointType, APointsType>::center() const
|
||||
{
|
||||
return (this->min + this->max) / 2;
|
||||
}
|
||||
template Point BoundingBoxBase<Point, Points>::center() const;
|
||||
template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
|
||||
template <class PointType> PointType
|
||||
BoundingBox3Base<PointType>::center() const
|
||||
{
|
||||
return (this->min + this->max) / 2;
|
||||
}
|
||||
template Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
|
||||
template <class PointType> coordf_t
|
||||
BoundingBox3Base<PointType>::max_size() const
|
||||
{
|
||||
PointType s = size();
|
||||
return std::max(s.x(), std::max(s.y(), s.z()));
|
||||
}
|
||||
template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
void BoundingBox::align_to_grid(const coord_t cell_size)
|
||||
{
|
||||
if (this->defined) {
|
||||
min.x() = Slic3r::align_to_grid(min.x(), cell_size);
|
||||
min.y() = Slic3r::align_to_grid(min.y(), cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
BoundingBoxf3 BoundingBoxf3::transformed(const Transform3d& matrix) const
|
||||
{
|
||||
typedef Eigen::Matrix<double, 3, 8, Eigen::DontAlign> Vertices;
|
||||
|
||||
Vertices src_vertices;
|
||||
src_vertices(0, 0) = min.x(); src_vertices(1, 0) = min.y(); src_vertices(2, 0) = min.z();
|
||||
src_vertices(0, 1) = max.x(); src_vertices(1, 1) = min.y(); src_vertices(2, 1) = min.z();
|
||||
src_vertices(0, 2) = max.x(); src_vertices(1, 2) = max.y(); src_vertices(2, 2) = min.z();
|
||||
src_vertices(0, 3) = min.x(); src_vertices(1, 3) = max.y(); src_vertices(2, 3) = min.z();
|
||||
src_vertices(0, 4) = min.x(); src_vertices(1, 4) = min.y(); src_vertices(2, 4) = max.z();
|
||||
src_vertices(0, 5) = max.x(); src_vertices(1, 5) = min.y(); src_vertices(2, 5) = max.z();
|
||||
src_vertices(0, 6) = max.x(); src_vertices(1, 6) = max.y(); src_vertices(2, 6) = max.z();
|
||||
src_vertices(0, 7) = min.x(); src_vertices(1, 7) = max.y(); src_vertices(2, 7) = max.z();
|
||||
|
||||
Vertices dst_vertices = matrix * src_vertices.colwise().homogeneous();
|
||||
|
||||
Vec3d v_min(dst_vertices(0, 0), dst_vertices(1, 0), dst_vertices(2, 0));
|
||||
Vec3d v_max = v_min;
|
||||
|
||||
for (int i = 1; i < 8; ++i)
|
||||
{
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
v_min(j) = std::min(v_min(j), dst_vertices(j, i));
|
||||
v_max(j) = std::max(v_max(j), dst_vertices(j, i));
|
||||
}
|
||||
}
|
||||
|
||||
return BoundingBoxf3(v_min, v_max);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
#ifndef slic3r_BoundingBox_hpp_
|
||||
#define slic3r_BoundingBox_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Exception.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <ostream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template <typename PointType, typename APointsType = std::vector<PointType>>
|
||||
class BoundingBoxBase
|
||||
{
|
||||
public:
|
||||
using PointsType = APointsType;
|
||||
PointType min;
|
||||
PointType max;
|
||||
bool defined;
|
||||
|
||||
BoundingBoxBase() : min(PointType::Zero()), max(PointType::Zero()), defined(false) {}
|
||||
BoundingBoxBase(const PointType &pmin, const PointType &pmax) :
|
||||
min(pmin), max(pmax), defined(pmin.x() < pmax.x() && pmin.y() < pmax.y()) {}
|
||||
BoundingBoxBase(const PointType &p1, const PointType &p2, const PointType &p3) :
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const PointsType &points)
|
||||
: BoundingBoxBase(points.begin(), points.end())
|
||||
{}
|
||||
|
||||
void reset() { this->defined = false; this->min = PointType::Zero(); this->max = PointType::Zero(); }
|
||||
void merge(const PointType &point);
|
||||
void merge(const PointsType &points);
|
||||
void merge(const BoundingBoxBase<PointType, PointsType> &bb);
|
||||
void scale(double factor);
|
||||
PointType size() const;
|
||||
double radius() const;
|
||||
double area() const { return double(this->max(0) - this->min(0)) * (this->max(1) - this->min(1)); } // BBS
|
||||
void translate(coordf_t x, coordf_t y) { assert(this->defined); PointType v(x, y); this->min += v; this->max += v; }
|
||||
void translate(const PointType &v) { this->min += v; this->max += v; }
|
||||
void offset(coordf_t delta);
|
||||
BoundingBoxBase<PointType, PointsType> inflated(coordf_t delta) const throw() { BoundingBoxBase<PointType, PointsType> out(*this); out.offset(delta); return out; }
|
||||
PointType center() const;
|
||||
bool contains(const PointType &point) const {
|
||||
return point.x() >= this->min.x() && point.x() <= this->max.x()
|
||||
&& point.y() >= this->min.y() && point.y() <= this->max.y();
|
||||
}
|
||||
bool contains(const BoundingBoxBase<PointType, PointsType> &other) const {
|
||||
return contains(other.min) && contains(other.max);
|
||||
}
|
||||
bool overlap(const BoundingBoxBase<PointType, PointsType> &other) const {
|
||||
return ! (this->max.x() < other.min.x() || this->min.x() > other.max.x() ||
|
||||
this->max.y() < other.min.y() || this->min.y() > other.max.y());
|
||||
}
|
||||
PointType operator[](size_t idx) const {
|
||||
switch (idx) {
|
||||
case 0:
|
||||
return min;
|
||||
break;
|
||||
case 1:
|
||||
return PointType(max(0), min(1));
|
||||
break;
|
||||
case 2:
|
||||
return max;
|
||||
break;
|
||||
case 3:
|
||||
return PointType(min(0), max(1));
|
||||
break;
|
||||
default:
|
||||
return PointType();
|
||||
break;
|
||||
}
|
||||
return PointType();
|
||||
}
|
||||
bool operator==(const BoundingBoxBase<PointType, PointsType> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
|
||||
bool operator!=(const BoundingBoxBase<PointType, PointsType> &rhs) { return ! (*this == rhs); }
|
||||
friend std::ostream &operator<<(std::ostream &os, const BoundingBoxBase &bbox)
|
||||
{
|
||||
os << "[" << bbox.max(0) - bbox.min(0) << " x " << bbox.max(1) - bbox.min(1) << "] from (" << bbox.min(0) << ", " << bbox.min(1) << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
// to access construct()
|
||||
friend BoundingBox get_extents<false>(const Points &pts);
|
||||
friend BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
// The output bounding box is expected to be set to "undefined" initially.
|
||||
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
|
||||
static void construct(BoundingBoxType &out, It from, It to)
|
||||
{
|
||||
if (from != to) {
|
||||
auto it = from;
|
||||
out.min = it->template cast<typename PointType::Scalar>();
|
||||
out.max = out.min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointType::Scalar>();
|
||||
out.min = out.min.cwiseMin(vec);
|
||||
out.max = out.max.cwiseMax(vec);
|
||||
}
|
||||
out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class PointType>
|
||||
class BoundingBox3Base : public BoundingBoxBase<PointType, std::vector<PointType>>
|
||||
{
|
||||
public:
|
||||
using PointsType = std::vector<PointType>;
|
||||
|
||||
BoundingBox3Base() : BoundingBoxBase<PointType>() {}
|
||||
BoundingBox3Base(const PointType &pmin, const PointType &pmax) :
|
||||
BoundingBoxBase<PointType>(pmin, pmax)
|
||||
{ if (pmin.z() >= pmax.z()) BoundingBoxBase<PointType>::defined = false; }
|
||||
BoundingBox3Base(const PointType &p1, const PointType &p2, const PointType &p3) :
|
||||
BoundingBoxBase<PointType>(p1, p1) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
|
||||
{
|
||||
if (from == to)
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointType::Scalar>();
|
||||
this->max = this->min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointType::Scalar>();
|
||||
this->min = this->min.cwiseMin(vec);
|
||||
this->max = this->max.cwiseMax(vec);
|
||||
}
|
||||
this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y()) && (this->min.z() < this->max.z());
|
||||
}
|
||||
|
||||
BoundingBox3Base(const PointsType &points)
|
||||
: BoundingBox3Base(points.begin(), points.end())
|
||||
{}
|
||||
|
||||
Polygon polygon(bool is_scaled = false) const;//BBS: 2D footprint polygon
|
||||
void merge(const PointType &point);
|
||||
void merge(const PointsType &points);
|
||||
void merge(const BoundingBox3Base<PointType> &bb);
|
||||
PointType size() const;
|
||||
double radius() const;
|
||||
void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointType v(x, y, z); this->min += v; this->max += v; }
|
||||
void translate(const Vec3d &v) { this->min += v; this->max += v; }
|
||||
void offset(coordf_t delta);
|
||||
BoundingBox3Base<PointType> inflated(coordf_t delta) const throw() { BoundingBox3Base<PointType> out(*this); out.offset(delta); return out; }
|
||||
PointType center() const;
|
||||
coordf_t max_size() const;
|
||||
|
||||
bool contains(const PointType &point) const {
|
||||
return BoundingBoxBase<PointType>::contains(point) && point.z() >= this->min.z() && point.z() <= this->max.z();
|
||||
}
|
||||
|
||||
bool contains(const BoundingBox3Base<PointType>& other) const {
|
||||
return contains(other.min) && contains(other.max);
|
||||
}
|
||||
|
||||
// Intersects without boundaries.
|
||||
bool intersects(const BoundingBox3Base<PointType>& other) const {
|
||||
return this->min.x() < other.max.x() && this->max.x() > other.min.x() && this->min.y() < other.max.y() && this->max.y() > other.min.y() &&
|
||||
this->min.z() < other.max.z() && this->max.z() > other.min.z();
|
||||
}
|
||||
};
|
||||
|
||||
// Will prevent warnings caused by non existing definition of template in hpp
|
||||
extern template void BoundingBoxBase<Point, Points>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Point, Points>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Point, Points>::merge(const Point &point);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
extern template void BoundingBoxBase<Point, Points>::merge(const Points &points);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
extern template void BoundingBoxBase<Point, Points>::merge(const BoundingBoxBase<Point, Points> &bb);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
extern template Point BoundingBoxBase<Point, Points>::size() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
extern template double BoundingBoxBase<Point, Points>::radius() const;
|
||||
extern template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
extern template Point BoundingBoxBase<Point, Points>::center() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
extern template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
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 Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
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 Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
class BoundingBox : public BoundingBoxBase<Point, Points>
|
||||
{
|
||||
public:
|
||||
void polygon(Polygon* polygon) const;
|
||||
Polygon polygon() const;
|
||||
BoundingBox rotated(double angle) const;
|
||||
BoundingBox rotated(double angle, const Point ¢er) const;
|
||||
void rotate(double angle) { (*this) = this->rotated(angle); }
|
||||
void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); }
|
||||
// Align the min corner to a grid of cell_size x cell_size cells,
|
||||
// to encompass the original bounding box.
|
||||
void align_to_grid(const coord_t cell_size);
|
||||
|
||||
BoundingBox() : BoundingBoxBase<Point, Points>() {}
|
||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point, Points>(pmin, pmax) {}
|
||||
BoundingBox(const Points &points) : BoundingBoxBase<Point, Points>(points) {}
|
||||
|
||||
BoundingBox inflated(coordf_t delta) const noexcept { BoundingBox out(*this); out.offset(delta); return out; }
|
||||
|
||||
BoundingBox scaled(double factor) const;
|
||||
|
||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
};
|
||||
|
||||
using BoundingBoxes = std::vector<BoundingBox>;
|
||||
|
||||
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) {}
|
||||
};
|
||||
|
||||
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) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
{
|
||||
public:
|
||||
using BoundingBox3Base::BoundingBox3Base;
|
||||
|
||||
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
||||
};
|
||||
|
||||
template<typename PointType, typename PointsType>
|
||||
inline bool empty(const BoundingBoxBase<PointType, PointsType> &bb)
|
||||
{
|
||||
return ! bb.defined || bb.min.x() >= bb.max.x() || bb.min.y() >= bb.max.y();
|
||||
}
|
||||
|
||||
template<typename PointType>
|
||||
inline bool empty(const BoundingBox3Base<PointType> &bb)
|
||||
{
|
||||
return ! bb.defined || bb.min.x() >= bb.max.x() || bb.min.y() >= bb.max.y() || bb.min.z() >= bb.max.z();
|
||||
}
|
||||
|
||||
inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
|
||||
template<class T = coord_t>
|
||||
BoundingBoxBase<Vec<2, T>> scaled(const BoundingBoxf &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = coord_t>
|
||||
BoundingBox3Base<Vec<3, T>> scaled(const BoundingBoxf3 &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = double>
|
||||
BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBox &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = double>
|
||||
BoundingBox3Base<Vec<3, T>> unscaled(const BoundingBox3 &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBoxBase<Tin> &b)
|
||||
{
|
||||
return BoundingBoxBase<Vec<2, Tout>>{b.min.template cast<Tout>(),
|
||||
b.max.template cast<Tout>()};
|
||||
}
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBox3Base<Tin> &b)
|
||||
{
|
||||
return BoundingBox3Base<Vec<3, Tout>>{b.min.template cast<Tout>(),
|
||||
b.max.template cast<Tout>()};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// Serialization through the Cereal library
|
||||
namespace cereal {
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox3 &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,474 @@
|
||||
#include "BridgeDetector.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BridgeDetector::BridgeDetector(
|
||||
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.
|
||||
lower_slices(_lower_slices),
|
||||
spacing(_spacing)
|
||||
{
|
||||
this->expolygons_owned.push_back(std::move(_expolygon));
|
||||
initialize();
|
||||
}
|
||||
|
||||
BridgeDetector::BridgeDetector(
|
||||
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.
|
||||
lower_slices(_lower_slices),
|
||||
spacing(_spacing)
|
||||
{
|
||||
initialize();
|
||||
}
|
||||
|
||||
void BridgeDetector::initialize()
|
||||
{
|
||||
// 5 degrees stepping
|
||||
this->resolution = PI/36.0;
|
||||
// output angle not known
|
||||
this->angle = -1.;
|
||||
|
||||
// Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors.
|
||||
Polygons grown = offset(this->expolygons, float(this->spacing));
|
||||
|
||||
// Detect possible anchoring edges of this bridging region.
|
||||
// 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()).
|
||||
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 %zu support(s)\n", this->_edges.size());
|
||||
#endif
|
||||
|
||||
// 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, union_safety_offset(this->lower_slices));
|
||||
|
||||
/*
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("bridge.svg",
|
||||
expolygons => [ $self->expolygon ],
|
||||
red_expolygons => $self->lower_slices,
|
||||
polylines => $self->_edges,
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
bool BridgeDetector::detect_angle(double bridge_direction_override)
|
||||
{
|
||||
if (this->_edges.empty() || this->_anchor_regions.empty())
|
||||
// The bridging region is completely in the air, there are no anchors available at the layer below.
|
||||
return false;
|
||||
|
||||
std::vector<BridgeDirection> candidates;
|
||||
if (bridge_direction_override == 0.) {
|
||||
std::vector<double> angles = bridge_direction_candidates();
|
||||
candidates.reserve(angles.size());
|
||||
for (size_t i = 0; i < angles.size(); ++ i)
|
||||
candidates.emplace_back(BridgeDirection(angles[i]));
|
||||
} else
|
||||
candidates.emplace_back(BridgeDirection(bridge_direction_override));
|
||||
|
||||
/* Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
we'll use this one to clip our test lines and be sure that their endpoints
|
||||
are inside the anchors and not on their contours leading to false negatives. */
|
||||
Polygons clip_area = offset(this->expolygons, 0.5f * float(this->spacing));
|
||||
|
||||
/* we'll now try several directions using a rudimentary visibility check:
|
||||
bridge in several directions and then sum the length of lines having both
|
||||
endpoints within anchors */
|
||||
|
||||
bool have_coverage = false;
|
||||
for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle)
|
||||
{
|
||||
const double angle = candidates[i_angle].angle;
|
||||
|
||||
Lines lines;
|
||||
{
|
||||
// Get an oriented bounding box around _anchor_regions.
|
||||
BoundingBox bbox = get_extents_rotated(this->_anchor_regions, - angle);
|
||||
// Cover the region with line segments.
|
||||
lines.reserve((bbox.max(1) - bbox.min(1) + this->spacing) / this->spacing);
|
||||
double s = sin(angle);
|
||||
double c = cos(angle);
|
||||
//FIXME Vojtech: The lines shall be spaced half the line width from the edge, but then
|
||||
// some of the test cases fail. Need to adjust the test cases then?
|
||||
// for (coord_t y = bbox.min(1) + this->spacing / 2; y <= bbox.max(1); y += this->spacing)
|
||||
for (coord_t y = bbox.min(1); y <= bbox.max(1); y += this->spacing)
|
||||
lines.push_back(Line(
|
||||
Point((coord_t)round(c * bbox.min(0) - s * y), (coord_t)round(c * y + s * bbox.min(0))),
|
||||
Point((coord_t)round(c * bbox.max(0) - s * y), (coord_t)round(c * y + s * bbox.max(0)))));
|
||||
}
|
||||
|
||||
double total_length = 0;
|
||||
double max_length = 0;
|
||||
{
|
||||
Lines clipped_lines = intersection_ln(lines, clip_area);
|
||||
size_t archored_line_num = 0;
|
||||
for (size_t i = 0; i < clipped_lines.size(); ++i) {
|
||||
const Line &line = clipped_lines[i];
|
||||
if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) {
|
||||
// This line could be anchored.
|
||||
double len = line.length();
|
||||
total_length += len;
|
||||
max_length = std::max(max_length, len);
|
||||
archored_line_num++;
|
||||
}
|
||||
}
|
||||
if (clipped_lines.size() > 0 && archored_line_num > 0) {
|
||||
candidates[i_angle].archored_percent = (double)archored_line_num / (double)clipped_lines.size();
|
||||
}
|
||||
}
|
||||
if (total_length == 0.)
|
||||
continue;
|
||||
|
||||
have_coverage = true;
|
||||
// Sum length of bridged lines.
|
||||
candidates[i_angle].coverage = total_length;
|
||||
/* The following produces more correct results in some cases and more broken in others.
|
||||
TODO: investigate, as it looks more reliable than line clipping. */
|
||||
// $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0;
|
||||
// max length of bridged lines
|
||||
candidates[i_angle].max_length = max_length;
|
||||
}
|
||||
|
||||
// if no direction produced coverage, then there's no bridge direction
|
||||
if (! have_coverage)
|
||||
return false;
|
||||
|
||||
// sort directions by coverage - most coverage first
|
||||
std::sort(candidates.begin(), candidates.end());
|
||||
|
||||
// if any other direction is within extrusion width of coverage, prefer it if shorter
|
||||
// TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred?
|
||||
size_t i_best = 0;
|
||||
// for (size_t i = 1; i < candidates.size() && abs(candidates[i_best].archored_percent - candidates[i].archored_percent) < EPSILON; ++ i)
|
||||
for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ i)
|
||||
if (candidates[i].max_length < candidates[i_best].max_length)
|
||||
i_best = i;
|
||||
|
||||
this->angle = candidates[i_best].angle;
|
||||
if (this->angle >= PI)
|
||||
this->angle -= PI;
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<double> BridgeDetector::bridge_direction_candidates() const
|
||||
{
|
||||
// we test angles according to configured resolution
|
||||
std::vector<double> angles;
|
||||
for (int i = 0; i <= PI/this->resolution; ++i)
|
||||
angles.push_back(i * this->resolution);
|
||||
|
||||
// we also test angles of each bridge contour
|
||||
{
|
||||
Lines lines = to_lines(this->expolygons);
|
||||
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line)
|
||||
angles.push_back(line->direction());
|
||||
}
|
||||
|
||||
/* we also test angles of each open supporting edge
|
||||
(this finds the optimal angle for C-shaped supports) */
|
||||
for (const Polyline &edge : this->_edges)
|
||||
if (edge.first_point() != edge.last_point())
|
||||
angles.push_back(Line(edge.first_point(), edge.last_point()).direction());
|
||||
|
||||
// remove duplicates
|
||||
double min_resolution = PI/180.0; // 1 degree
|
||||
std::sort(angles.begin(), angles.end());
|
||||
for (size_t i = 1; i < angles.size(); ++i) {
|
||||
if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) {
|
||||
angles.erase(angles.begin() + i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
/* compare first value with last one and remove the greatest one (PI)
|
||||
in case they are parallel (PI, 0) */
|
||||
if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution))
|
||||
angles.pop_back();
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
/*
|
||||
static void get_trapezoids(const ExPolygon &expoly, Polygons* polygons) const
|
||||
{
|
||||
ExPolygons expp;
|
||||
expp.push_back(expoly);
|
||||
boost::polygon::get_trapezoids(*polygons, expp);
|
||||
}
|
||||
|
||||
void ExPolygon::get_trapezoids(ExPolygon clone, Polygons* polygons, double angle) const
|
||||
{
|
||||
clone.rotate(PI/2 - angle, Point(0,0));
|
||||
clone.get_trapezoids(polygons);
|
||||
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
|
||||
polygon->rotate(-(PI/2 - angle), Point(0,0));
|
||||
}
|
||||
*/
|
||||
|
||||
// This algorithm may return more trapezoids than necessary
|
||||
// (i.e. it may break a single trapezoid in several because
|
||||
// other parts of the object have x coordinates in the middle)
|
||||
static void get_trapezoids2(const ExPolygon& expoly, Polygons* polygons)
|
||||
{
|
||||
Polygons src_polygons = to_polygons(expoly);
|
||||
// get all points of this ExPolygon
|
||||
const Points pp = to_points(src_polygons);
|
||||
|
||||
// build our bounding box
|
||||
BoundingBox bb(pp);
|
||||
|
||||
// get all x coordinates
|
||||
std::vector<coord_t> xx;
|
||||
xx.reserve(pp.size());
|
||||
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p)
|
||||
xx.push_back(p->x());
|
||||
std::sort(xx.begin(), xx.end());
|
||||
|
||||
// find trapezoids by looping from first to next-to-last coordinate
|
||||
Polygons rectangle;
|
||||
rectangle.emplace_back(Polygon());
|
||||
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end()-1; ++x) {
|
||||
coord_t next_x = *(x + 1);
|
||||
if (*x != next_x) {
|
||||
// intersect with rectangle
|
||||
// append results to return value
|
||||
rectangle.front() = { { *x, bb.min.y() }, { next_x, bb.min.y() }, { next_x, bb.max.y() }, { *x, bb.max.y() } };
|
||||
polygons_append(*polygons, intersection(rectangle, src_polygons));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_trapezoids2(const ExPolygon &expoly, Polygons* polygons, double angle)
|
||||
{
|
||||
ExPolygon clone = expoly;
|
||||
clone.rotate(PI/2 - angle, Point(0,0));
|
||||
get_trapezoids2(clone, polygons);
|
||||
for (Polygon &polygon : *polygons)
|
||||
polygon.rotate(-(PI/2 - angle), Point(0,0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void get_trapezoids3_half(const ExPolygon& expoly, Polygons* polygons, float spacing)
|
||||
{
|
||||
|
||||
// get all points of this ExPolygon
|
||||
Points pp = to_points(expoly);
|
||||
|
||||
if (pp.empty()) return;
|
||||
|
||||
// build our bounding box
|
||||
BoundingBox bb(pp);
|
||||
|
||||
// get all x coordinates
|
||||
coord_t min_x = pp[0].x(), max_x = pp[0].x();
|
||||
std::vector<coord_t> xx;
|
||||
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) {
|
||||
if (min_x > p->x()) min_x = p->x();
|
||||
if (max_x < p->x()) max_x = p->x();
|
||||
}
|
||||
for (coord_t x = min_x; x < max_x - (coord_t)(spacing / 2); x += (coord_t)spacing) {
|
||||
xx.push_back(x);
|
||||
}
|
||||
xx.push_back(max_x);
|
||||
//std::sort(xx.begin(), xx.end());
|
||||
|
||||
// find trapezoids by looping from first to next-to-last coordinate
|
||||
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end() - 1; ++x) {
|
||||
coord_t next_x = *(x + 1);
|
||||
if (*x == next_x) continue;
|
||||
|
||||
// build rectangle
|
||||
Polygon poly;
|
||||
poly.points.resize(4);
|
||||
poly[0].x() = *x + (coord_t)spacing / 4;
|
||||
poly[0].y() = bb.min(1);
|
||||
poly[1].x() = next_x - (coord_t)spacing / 4;
|
||||
poly[1].y() = bb.min(1);
|
||||
poly[2].x() = next_x - (coord_t)spacing / 4;
|
||||
poly[2].y() = bb.max(1);
|
||||
poly[3].x() = *x + (coord_t)spacing / 4;
|
||||
poly[3].y() = bb.max(1);
|
||||
|
||||
// intersect with this expolygon
|
||||
// append results to return value
|
||||
polygons_append(*polygons, intersection(Polygons{ poly }, to_polygons(expoly)));
|
||||
}
|
||||
}
|
||||
|
||||
Polygons BridgeDetector::coverage(double angle, bool precise) const
|
||||
{
|
||||
if (angle == -1)
|
||||
angle = this->angle;
|
||||
|
||||
Polygons covered;
|
||||
|
||||
if (angle != -1) {
|
||||
// Get anchors, convert them to Polygons and rotate them.
|
||||
Polygons anchors = to_polygons(this->_anchor_regions);
|
||||
polygons_rotate(anchors, PI / 2.0 - angle);
|
||||
//same for region which do not need bridging
|
||||
//Polygons supported_area = diff(this->lower_slices.expolygons, this->_anchor_regions, true);
|
||||
//polygons_rotate(anchors, PI / 2.0 - angle);
|
||||
|
||||
for (ExPolygon expolygon : this->expolygons) {
|
||||
// Clone our expolygon and rotate it so that we work with vertical lines.
|
||||
expolygon.rotate(PI / 2.0 - angle);
|
||||
// Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
// we'll use this one to generate our trapezoids and be sure that their vertices
|
||||
// are inside the anchors and not on their contours leading to false negatives.
|
||||
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
|
||||
// Compute trapezoids according to a vertical orientation
|
||||
Polygons trapezoids;
|
||||
if (!precise) get_trapezoids2(expoly, &trapezoids, PI / 2);
|
||||
else get_trapezoids3_half(expoly, &trapezoids, float(this->spacing));
|
||||
for (Polygon &trapezoid : trapezoids) {
|
||||
size_t n_supported = 0;
|
||||
if (!precise) {
|
||||
// not nice, we need a more robust non-numeric check
|
||||
// imporvment 1: take into account when we go in the supported area.
|
||||
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
|
||||
if (supported_line.length() >= this->spacing)
|
||||
++n_supported;
|
||||
} else {
|
||||
Polygons intersects = intersection(Polygons{trapezoid}, anchors);
|
||||
n_supported = intersects.size();
|
||||
|
||||
if (n_supported >= 2) {
|
||||
// trim it to not allow to go outside of the intersections
|
||||
BoundingBox center_bound = intersects[0].bounding_box();
|
||||
coord_t min_y = center_bound.center()(1), max_y = center_bound.center()(1);
|
||||
for (Polygon &poly_bound : intersects) {
|
||||
center_bound = poly_bound.bounding_box();
|
||||
if (min_y > center_bound.center()(1)) min_y = center_bound.center()(1);
|
||||
if (max_y < center_bound.center()(1)) max_y = center_bound.center()(1);
|
||||
}
|
||||
coord_t min_x = trapezoid[0](0), max_x = trapezoid[0](0);
|
||||
for (Point &p : trapezoid.points) {
|
||||
if (min_x > p(0)) min_x = p(0);
|
||||
if (max_x < p(0)) max_x = p(0);
|
||||
}
|
||||
//add what get_trapezoids3 has removed (+EPSILON)
|
||||
min_x -= (this->spacing / 4 + 1);
|
||||
max_x += (this->spacing / 4 + 1);
|
||||
coord_t mid_x = (min_x + max_x) / 2;
|
||||
for (Point &p : trapezoid.points) {
|
||||
if (p(1) < min_y) p(1) = min_y;
|
||||
if (p(1) > max_y) p(1) = max_y;
|
||||
if (p(0) > min_x && p(0) < mid_x) p(0) = min_x;
|
||||
if (p(0) < max_x && p(0) > mid_x) p(0) = max_x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (n_supported >= 2) {
|
||||
//add it
|
||||
covered.push_back(std::move(trapezoid));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
|
||||
// instead of exact overlaps.
|
||||
covered = union_(covered);
|
||||
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
|
||||
polygons_rotate(covered, -(PI/2.0 - angle));
|
||||
//covered = intersection(this->expolygons, covered);
|
||||
#if 0
|
||||
{
|
||||
my @lines = map @{$_->lines}, @$trapezoids;
|
||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
|
||||
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"coverage_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchor_regions,
|
||||
red_expolygons => $coverage,
|
||||
lines => \@lines,
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return covered;
|
||||
}
|
||||
|
||||
/* This method returns the bridge edges (as polylines) that are not supported
|
||||
but would allow the entire bridge area to be bridged with detected angle
|
||||
if supported too */
|
||||
void
|
||||
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, 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)
|
||||
Lines unsupported_lines = to_lines(diff_pl(to_polylines(*it_expoly), grown_lower));
|
||||
/* Split into individual segments and filter out edges parallel to the bridging angle
|
||||
TODO: angle tolerance should probably be based on segment length and flow width,
|
||||
so that we build supports whenever there's a chance that at least one or two bridge
|
||||
extrusions would be anchored within such length (i.e. a slightly non-parallel bridging
|
||||
direction might still benefit from anchors if long enough)
|
||||
double angle_tolerance = PI / 180.0 * 5.0; */
|
||||
for (const Line &line : unsupported_lines)
|
||||
if (! Slic3r::Geometry::directions_parallel(line.direction(), angle)) {
|
||||
unsupported->emplace_back(Polyline());
|
||||
unsupported->back().points.emplace_back(line.a);
|
||||
unsupported->back().points.emplace_back(line.b);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"unsupported_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchor_regions,
|
||||
red_expolygons => union_ex($grown_lower),
|
||||
no_arrows => 1,
|
||||
polylines => \@bridge_edges,
|
||||
red_polylines => $unsupported,
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Polylines
|
||||
BridgeDetector::unsupported_edges(double angle) const
|
||||
{
|
||||
Polylines pp;
|
||||
this->unsupported_edges(angle, &pp);
|
||||
return pp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#ifndef slic3r_BridgeDetector_hpp_
|
||||
#define slic3r_BridgeDetector_hpp_
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "PrincipalComponents2D.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// The bridge detector optimizes a direction of bridges over a region or a set of regions.
|
||||
// A bridge direction is considered optimal, if the length of the lines strang over the region is maximal.
|
||||
// This is optimal if the bridge is supported in a single direction only, but
|
||||
// it may not likely be optimal, if the bridge region is supported from all sides. Then an optimal
|
||||
// solution would find a direction with shortest bridges.
|
||||
// The bridge orientation is measured CCW from the X axis.
|
||||
class BridgeDetector {
|
||||
public:
|
||||
// The non-grown holes.
|
||||
const ExPolygons &expolygons;
|
||||
// In case the caller gaves us the input polygons by a value, make a copy.
|
||||
ExPolygons expolygons_owned;
|
||||
// Lower slices, all regions.
|
||||
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.
|
||||
double resolution;
|
||||
// The final optimal angle.
|
||||
double angle;
|
||||
|
||||
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, bool precise = true) const;
|
||||
void unsupported_edges(double angle, Polylines* unsupported) const;
|
||||
Polylines unsupported_edges(double angle = -1) const;
|
||||
|
||||
private:
|
||||
// Suppress warning "assignment operator could not be generated"
|
||||
BridgeDetector& operator=(const BridgeDetector &);
|
||||
|
||||
void initialize();
|
||||
|
||||
struct BridgeDirection {
|
||||
BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.), archored_percent(0.){}
|
||||
// the best direction is the one causing most lines to be bridged (thus most coverage)
|
||||
bool operator<(const BridgeDirection &other) const {
|
||||
// Initial sort by coverage only - comparator must obey strict weak ordering
|
||||
return this->coverage > other.coverage;//this->archored_percent > other.archored_percent;
|
||||
};
|
||||
double angle;
|
||||
double coverage;
|
||||
double max_length;
|
||||
double archored_percent;
|
||||
};
|
||||
|
||||
// Get possible briging direction candidates.
|
||||
std::vector<double> bridge_direction_candidates() const;
|
||||
|
||||
// Open lines representing the supporting edges.
|
||||
Polylines _edges;
|
||||
// Closed polygons representing the supporting areas.
|
||||
ExPolygons _anchor_regions;
|
||||
};
|
||||
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area)
|
||||
{
|
||||
if (floating_edges.empty()) {
|
||||
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
|
||||
auto [pc1, pc2] = compute_principal_components(overhang_area);
|
||||
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
return {Vec2d{1.0,0.0}, 0.0};
|
||||
} else {
|
||||
return {pc2.normalized().cast<double>(), 0.0};
|
||||
}
|
||||
}
|
||||
|
||||
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
|
||||
std::unordered_map<double, Vec2d> directions{};
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d normal = l.normal().cast<double>().normalized();
|
||||
double quantized_angle = std::ceil(std::atan2(normal.y(),normal.x()) * 1000.0);
|
||||
directions.emplace(quantized_angle, normal);
|
||||
}
|
||||
std::vector<std::pair<Vec2d, double>> direction_costs{};
|
||||
// it is acutally cost of a perpendicular bridge direction - we find the minimal cost and then return the perpendicular dir
|
||||
for (const auto& d : directions) {
|
||||
direction_costs.emplace_back(d.second, 0.0);
|
||||
}
|
||||
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d line = (l.b - l.a).cast<double>();
|
||||
for (auto &dir_cost : direction_costs) {
|
||||
// the dot product already contains the length of the line. dir_cost.first is normalized.
|
||||
dir_cost.second += std::abs(line.dot(dir_cost.first));
|
||||
}
|
||||
}
|
||||
|
||||
Vec2d result_dir = Vec2d::Ones();
|
||||
double min_cost = std::numeric_limits<double>::max();
|
||||
for (const auto &cost : direction_costs) {
|
||||
if (cost.second < min_cost) {
|
||||
// now flip the orientation back and return the direction of the bridge extrusions
|
||||
result_dir = Vec2d{cost.first.y(), -cost.first.x()};
|
||||
min_cost = cost.second;
|
||||
}
|
||||
}
|
||||
|
||||
return {result_dir, min_cost};
|
||||
};
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Lines floating_edges = to_lines(diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON))));
|
||||
return detect_bridging_direction(floating_edges, overhang_area);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,990 @@
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "MaterialType.hpp"
|
||||
#include "Model.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define BRIM_DEBUG_TO_SVG
|
||||
#endif
|
||||
|
||||
#if defined(BRIM_DEBUG_TO_SVG)
|
||||
#include "SVG.hpp"
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const PrintInstance &instance) {
|
||||
size_t dst_idx = dst.size();
|
||||
expolygons_append(dst, src);
|
||||
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
// BBS: generate brim area by objs
|
||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
||||
const PrintInstance& instance, const Print& print, std::map<ObjectID, ExPolygons>& brimAreaMap) {
|
||||
ExPolygons srcShifted = src;
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
||||
srcShifted[src_idx].translate(instance_shift);
|
||||
srcShifted = diff_ex(srcShifted, dst);
|
||||
//expolygons_append(dst, temp2);
|
||||
expolygons_append(brimAreaMap[instance.print_object->id()], std::move(srcShifted));
|
||||
}
|
||||
|
||||
static void append_and_translate(Polygons &dst, const Polygons &src, const PrintInstance &instance) {
|
||||
size_t dst_idx = dst.size();
|
||||
polygons_append(dst, src);
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
|
||||
//ORCA: Brim can follow the post-EFC outline when enabled.
|
||||
static bool use_brim_efc_outline(const PrintObject &object)
|
||||
{
|
||||
return object.config().brim_use_efc_outline.value
|
||||
&& object.config().elefant_foot_compensation.value > 0.
|
||||
&& object.config().elefant_foot_compensation_layers.value > 0
|
||||
&& object.config().raft_layers.value == 0;
|
||||
}
|
||||
|
||||
//ORCA: Helper for snapping painted ears to the EFC outline.
|
||||
static bool closest_point_on_expolygons(const ExPolygons &polygons, const Point &from, Point &closest_out)
|
||||
{
|
||||
double min_dist2 = std::numeric_limits<double>::max();
|
||||
bool found = false;
|
||||
|
||||
for (const ExPolygon &poly : polygons) {
|
||||
for (int i = 0; i < poly.num_contours(); ++i) {
|
||||
const Point *candidate = poly.contour_or_hole(i).closest_point(from);
|
||||
if (candidate == nullptr)
|
||||
continue;
|
||||
const int64_t dx = int64_t(candidate->x()) - int64_t(from.x());
|
||||
const int64_t dy = int64_t(candidate->y()) - int64_t(from.y());
|
||||
const double dist2 = double(dx * dx + dy * dy);
|
||||
if (dist2 < min_dist2) {
|
||||
min_dist2 = dist2;
|
||||
closest_out = *candidate;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
//ORCA: Helper for matching painted ears to their original island before EFC snapping.
|
||||
static int find_containing_expolygon_index(const ExPolygons &polygons, const Point &from)
|
||||
{
|
||||
for (size_t idx = 0; idx < polygons.size(); ++idx) {
|
||||
if (polygons[idx].contains(from))
|
||||
return int(idx);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//ORCA: Keep painted ear snapping on the matching island when using EFC outline.
|
||||
static bool closest_point_on_matching_island(const ExPolygons &raw_outline, const ExPolygons &efc_outline, const Point &from, Point &closest_out)
|
||||
{
|
||||
const int island_idx = find_containing_expolygon_index(raw_outline, from);
|
||||
if (island_idx >= 0) {
|
||||
ExPolygons island_outline = intersection_ex(efc_outline, raw_outline[island_idx]);
|
||||
if (!island_outline.empty())
|
||||
return closest_point_on_expolygons(island_outline, from, closest_out);
|
||||
}
|
||||
return closest_point_on_expolygons(efc_outline, from, closest_out);
|
||||
}
|
||||
//ORCA: Use post-processed first-layer slices (including EFC) for brim outline.
|
||||
// Returns ExPolygons of the bottom layer after all first-layer modifiers
|
||||
// (including elephant foot compensation, if enabled) have been applied.
|
||||
static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &print_object)
|
||||
{
|
||||
ExPolygons ex_polygons;
|
||||
for (LayerRegion *region : print_object.layers().front()->regions())
|
||||
Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON)));
|
||||
return ex_polygons;
|
||||
}
|
||||
//BBS adhesion coefficients from print object class
|
||||
double getadhesionCoeff(const PrintObject* printObject)
|
||||
{
|
||||
auto& insts = printObject->instances();
|
||||
auto objectVolumes = insts[0].model_instance->get_object()->volumes;
|
||||
|
||||
auto print = printObject->print();
|
||||
std::vector<size_t> extrudersFirstLayer;
|
||||
auto firstLayerRegions = printObject->layers().front()->regions();
|
||||
if (!firstLayerRegions.empty()) {
|
||||
for (const LayerRegion* regionPtr : firstLayerRegions) {
|
||||
if (regionPtr->has_extrusions())
|
||||
extrudersFirstLayer.push_back(regionPtr->region().extruder(frExternalPerimeter));
|
||||
}
|
||||
}
|
||||
double adhesionCoeff = 1;
|
||||
for (const ModelVolume* modelVolume : objectVolumes) {
|
||||
for (auto iter = extrudersFirstLayer.begin(); iter != extrudersFirstLayer.end(); iter++) {
|
||||
if (modelVolume->extruder_id() == *iter) {
|
||||
if (Model::extruderParamsMap.find(modelVolume->extruder_id()) != Model::extruderParamsMap.end()) {
|
||||
std::string filament_type = Model::extruderParamsMap.at(modelVolume->extruder_id()).materialName;
|
||||
double adhesion_coefficient = 1.0; // Default value
|
||||
MaterialType::get_adhesion_coefficient(filament_type, adhesion_coefficient);
|
||||
adhesionCoeff = adhesion_coefficient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return adhesionCoeff;
|
||||
/*
|
||||
def->enum_values.push_back("PLA");
|
||||
def->enum_values.push_back("PET");
|
||||
def->enum_values.push_back("ABS");
|
||||
def->enum_values.push_back("ASA");
|
||||
def->enum_values.push_back("TPU");//BBS
|
||||
def->enum_values.push_back("FLEX");
|
||||
def->enum_values.push_back("HIPS");
|
||||
def->enum_values.push_back("EDGE");
|
||||
def->enum_values.push_back("NGEN");
|
||||
def->enum_values.push_back("NYLON");
|
||||
def->enum_values.push_back("PVA");
|
||||
def->enum_values.push_back("PC");
|
||||
def->enum_values.push_back("PP");
|
||||
def->enum_values.push_back("PEI");
|
||||
def->enum_values.push_back("PEEK");
|
||||
def->enum_values.push_back("PEKK");
|
||||
def->enum_values.push_back("POM");
|
||||
def->enum_values.push_back("PSU");
|
||||
def->enum_values.push_back("PVDF");
|
||||
def->enum_values.push_back("SCAFF");
|
||||
*/
|
||||
}
|
||||
|
||||
// BBS: second moment of area of a polygon
|
||||
bool compSecondMoment(Polygon poly, Vec2d& sm)
|
||||
{
|
||||
if (poly.is_clockwise())
|
||||
poly.make_counter_clockwise();
|
||||
|
||||
sm = Vec2d(0., 0.);
|
||||
if (poly.points.size() >= 3) {
|
||||
Vec2d p1 = poly.points.back().cast<double>();
|
||||
for (const Point& p : poly.points) {
|
||||
Vec2d p2 = p.cast<double>();
|
||||
double a = cross2(p1, p2);
|
||||
|
||||
sm += Vec2d((p1.y() * p1.y() + p1.y() * p2.y() + p2.y() * p2.y()), (p1.x() * p1.x() + p1.x() * p2.x() + p2.x() * p2.x())) * a / 12;
|
||||
p1 = p2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// BBS: properties of an expolygon
|
||||
struct ExPolyProp
|
||||
{
|
||||
double aera = 0;
|
||||
Vec2d centroid;
|
||||
Vec2d secondMomentOfAreaRespectToCentroid;
|
||||
|
||||
};
|
||||
// BBS: second moment of area of an expolyon
|
||||
bool compSecondMoment(const ExPolygon& expoly, ExPolyProp& expolyProp)
|
||||
{
|
||||
double aera = expoly.contour.area();
|
||||
Vec2d cent = expoly.contour.centroid().cast<double>() * aera;
|
||||
Vec2d sm;
|
||||
if (!compSecondMoment(expoly.contour, sm))
|
||||
return false;
|
||||
|
||||
for (auto& hole : expoly.holes) {
|
||||
double a = hole.area();
|
||||
aera += hole.area();
|
||||
cent += hole.centroid().cast<double>() * a;
|
||||
Vec2d smh;
|
||||
if (compSecondMoment(hole, smh))
|
||||
sm += -smh;
|
||||
}
|
||||
|
||||
cent = cent / aera;
|
||||
sm = sm - Vec2d(cent.y() * cent.y(), cent.x() * cent.x()) * aera;
|
||||
expolyProp.aera = aera;
|
||||
expolyProp.centroid = cent;
|
||||
expolyProp.secondMomentOfAreaRespectToCentroid = sm;
|
||||
return true;
|
||||
}
|
||||
|
||||
// BBS: second moment of area of expolygons
|
||||
bool compSecondMoment(const ExPolygons& expolys, double& smExpolysX, double& smExpolysY)
|
||||
{
|
||||
if (expolys.empty()) return false;
|
||||
std::vector<ExPolyProp> props;
|
||||
for (const ExPolygon& expoly : expolys) {
|
||||
ExPolyProp prop;
|
||||
if (compSecondMoment(expoly, prop))
|
||||
props.push_back(prop);
|
||||
}
|
||||
if (props.empty())
|
||||
return false;
|
||||
double totalArea = 0.;
|
||||
Vec2d staticMoment(0., 0.);
|
||||
for (const ExPolyProp& prop : props) {
|
||||
totalArea += prop.aera;
|
||||
staticMoment += prop.centroid * prop.aera;
|
||||
}
|
||||
double totalCentroidX = staticMoment.x() / totalArea;
|
||||
double totalCentroidY = staticMoment.y() / totalArea;
|
||||
|
||||
smExpolysX = 0;
|
||||
smExpolysY = 0;
|
||||
for (const ExPolyProp& prop : props) {
|
||||
double deltaX = prop.centroid.x() - totalCentroidX;
|
||||
double deltaY = prop.centroid.y() - totalCentroidY;
|
||||
smExpolysX += prop.secondMomentOfAreaRespectToCentroid.x() + prop.aera * deltaY * deltaY;
|
||||
smExpolysY += prop.secondMomentOfAreaRespectToCentroid.y() + prop.aera * deltaX * deltaX;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
//BBS: config brimwidth by group of volumes
|
||||
double configBrimWidthByVolumeGroups(double adhesion, double maxSpeed, const std::vector<ModelVolume*> modelVolumePtrs, const ExPolygons& expolys, double &groupHeight)
|
||||
{
|
||||
// height of a group of volumes
|
||||
double height = 0;
|
||||
BoundingBoxf3 mergedBbx;
|
||||
for (const auto& modelVolumePtr : modelVolumePtrs) {
|
||||
if (modelVolumePtr->is_model_part()) {
|
||||
Slic3r::Transform3d t;
|
||||
if (modelVolumePtr->get_object()->instances.size() > 0)
|
||||
t = modelVolumePtr->get_object()->instances.front()->get_matrix() * modelVolumePtr->get_matrix();
|
||||
else
|
||||
t = modelVolumePtr->get_matrix();
|
||||
auto bbox = modelVolumePtr->mesh().transformed_bounding_box(t);
|
||||
mergedBbx.merge(bbox);
|
||||
}
|
||||
}
|
||||
auto bbox_size = mergedBbx.size();
|
||||
height = bbox_size(2);
|
||||
groupHeight = height;
|
||||
// second moment of the expolygons of the first layer of the volume group
|
||||
double Ixx = -1.e30, Iyy = -1.e30;
|
||||
if (!expolys.empty()) {
|
||||
if (!compSecondMoment(expolys, Ixx, Iyy))
|
||||
Ixx = Iyy = -1.e30;
|
||||
}
|
||||
Ixx = Ixx * SCALING_FACTOR * SCALING_FACTOR * SCALING_FACTOR * SCALING_FACTOR;
|
||||
Iyy = Iyy * SCALING_FACTOR * SCALING_FACTOR * SCALING_FACTOR * SCALING_FACTOR;
|
||||
|
||||
// bounding box of the expolygons of the first layer of the volume
|
||||
BoundingBox bbox2;
|
||||
for (const auto& expoly : expolys)
|
||||
bbox2.merge(get_extents(expoly.contour));
|
||||
const double& bboxX = bbox2.size()(0);
|
||||
const double& bboxY = bbox2.size()(1);
|
||||
double thermalLength = sqrt(bboxX * bboxX + bboxY * bboxY) * SCALING_FACTOR;
|
||||
double thermalLengthRef = Model::getThermalLength(modelVolumePtrs);
|
||||
|
||||
double height_to_area = std::max(height / Ixx * (bbox2.size()(1) * SCALING_FACTOR), height / Iyy * (bbox2.size()(0) * SCALING_FACTOR)) * height / 1920;
|
||||
double brim_width = adhesion * std::min(std::min(std::max(height_to_area * maxSpeed, thermalLength * 8. / thermalLengthRef * std::min(height, 30.) / 30.), 18.), 1.5 * thermalLength);
|
||||
// small brims are omitted
|
||||
if (brim_width < 5 && brim_width < 1.5 * thermalLength)
|
||||
brim_width = 0;
|
||||
// large brims are omitted
|
||||
if (brim_width > 18) brim_width = 18.;
|
||||
|
||||
return brim_width;
|
||||
}
|
||||
|
||||
// Generate ears
|
||||
// Ported from SuperSlicer: https://github.com/supermerill/SuperSlicer/blob/45d0532845b63cd5cefe7de7dc4ef0e0ed7e030a/src/libslic3r/Brim.cpp#L1116
|
||||
static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size_ear, coord_t ear_detection_length,
|
||||
coordf_t brim_ears_max_angle, bool is_outer_brim) {
|
||||
ExPolygons mouse_ears_ex;
|
||||
if (size_ear <= 0) {
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
// Detect places to put ears
|
||||
const coordf_t angle_threshold = (180 - brim_ears_max_angle) * PI / 180.0;
|
||||
Points pt_ears;
|
||||
for (const ExPolygon &poly : obj_expoly) {
|
||||
Polygon decimated_polygon = poly.contour;
|
||||
if (ear_detection_length > 0) {
|
||||
// decimate polygon
|
||||
Points points = poly.contour.points;
|
||||
points.push_back(points.front());
|
||||
points = MultiPoint::_douglas_peucker(points, ear_detection_length);
|
||||
if (points.size() > 4) { // don't decimate if it's going to be below 4 points, as it's surely enough to fill everything anyway
|
||||
points.erase(points.end() - 1);
|
||||
decimated_polygon.points = points;
|
||||
}
|
||||
}
|
||||
|
||||
append(pt_ears, is_outer_brim ? decimated_polygon.convex_points(angle_threshold)
|
||||
: decimated_polygon.concave_points(angle_threshold));
|
||||
}
|
||||
|
||||
// Then add ears
|
||||
// create ear pattern
|
||||
Polygon point_round;
|
||||
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
|
||||
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
|
||||
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
|
||||
}
|
||||
|
||||
// create ears
|
||||
for (Point &pt : pt_ears) {
|
||||
mouse_ears_ex.emplace_back();
|
||||
mouse_ears_ex.back().contour = point_round;
|
||||
mouse_ears_ex.back().contour.translate(pt);
|
||||
}
|
||||
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
|
||||
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
|
||||
{
|
||||
ExPolygons mouse_ears_ex;
|
||||
BrimPoints brim_ear_points = object->model_object()->brim_points;
|
||||
if (brim_ear_points.size() <= 0) {
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
//ORCA: Painted ears can snap to the EFC-adjusted outline when enabled.
|
||||
const bool use_efc_outline = use_brim_efc_outline(*object);
|
||||
const ExPolygons &raw_outline = object->layers().front()->lslices;
|
||||
//ORCA: Lazily computed EFC-adjusted bottom outline.
|
||||
//Stored separately so we can avoid recomputation unless EFC snapping is used.
|
||||
ExPolygons efc_outline_storage;
|
||||
const ExPolygons* efc_outline = nullptr;
|
||||
|
||||
const Geometry::Transformation& trsf = object->model_object()->instances[0]->get_transformation();
|
||||
Transform3d model_trsf = trsf.get_matrix_no_offset();
|
||||
const Point ¢er_offset = object->center_offset();
|
||||
model_trsf = model_trsf.pretranslate(Vec3d(- unscale<double>(center_offset.x()), - unscale<double>(center_offset.y()), 0));
|
||||
for (auto &pt : brim_ear_points) {
|
||||
Vec3f world_pos = pt.transform(trsf.get_matrix());
|
||||
if ( world_pos.z() > 0) continue;
|
||||
Polygon point_round;
|
||||
float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2;
|
||||
if (is_outer_brim) {
|
||||
double flowWidthScale = flowWidth / SCALING_FACTOR;
|
||||
brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2;
|
||||
}
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
|
||||
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
|
||||
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
|
||||
}
|
||||
mouse_ears_ex.emplace_back();
|
||||
mouse_ears_ex.back().contour = point_round;
|
||||
Vec3f pos = pt.transform(model_trsf);
|
||||
int32_t pt_x = scale_(pos.x());
|
||||
int32_t pt_y = scale_(pos.y());
|
||||
|
||||
//ORCA: Snap painted ears to the EFC-adjusted outline when enabled.
|
||||
if (use_efc_outline) {
|
||||
if (efc_outline == nullptr) {
|
||||
//ORCA: Compute EFC-adjusted outline lazily for painted ear snapping.
|
||||
efc_outline_storage = get_print_object_bottom_layer_expolygons(*object);
|
||||
efc_outline = &efc_outline_storage;
|
||||
}
|
||||
|
||||
if (!efc_outline->empty()) {
|
||||
Point closest_point;
|
||||
//ORCA: Snap within the matching island to avoid drifting to another island.
|
||||
if (closest_point_on_matching_island(
|
||||
raw_outline,
|
||||
*efc_outline,
|
||||
Point(pt_x, pt_y),
|
||||
closest_point)) {
|
||||
pt_x = closest_point.x();
|
||||
pt_y = closest_point.y();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mouse_ears_ex.back().contour.translate(Point(pt_x, pt_y));
|
||||
}
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
|
||||
//BBS: create all brims
|
||||
static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
const float no_brim_offset, std::map<ObjectID, ExPolygons>& brimAreaMap,
|
||||
std::map<ObjectID, ExPolygons>& supportBrimAreaMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders)
|
||||
{
|
||||
unsigned int support_material_extruder = printExtruders.front() + 1;
|
||||
Flow flow = print.brim_flow();
|
||||
|
||||
ExPolygons brim_area;
|
||||
ExPolygons no_brim_area;
|
||||
Polygons holes;
|
||||
|
||||
struct brimWritten {
|
||||
bool obj;
|
||||
bool sup;
|
||||
};
|
||||
std::map<ObjectID, brimWritten> brimToWrite;
|
||||
for (const auto& objectWithExtruder : objPrintVec)
|
||||
brimToWrite.insert({ objectWithExtruder.first, {true,true} });
|
||||
|
||||
ExPolygons objectIslands;
|
||||
for (unsigned int extruderNo : printExtruders) {
|
||||
++extruderNo;
|
||||
for (const auto& objectWithExtruder : objPrintVec) {
|
||||
const PrintObject* object = print.get_object(objectWithExtruder.first);
|
||||
const BrimType brim_type = object->config().brim_type.value;
|
||||
float brim_offset = scale_(object->config().brim_object_gap.value);
|
||||
double flowWidth = print.brim_flow().scaled_spacing() * SCALING_FACTOR;
|
||||
float brim_width = scale_(floor(object->config().brim_width.value / flowWidth / 2) * flowWidth * 2);
|
||||
const float scaled_flow_width = print.brim_flow().scaled_spacing();
|
||||
const float scaled_additional_brim_width = scale_(floor(5 / flowWidth / 2) * flowWidth * 2);
|
||||
const float scaled_half_min_adh_length = scale_(1.1);
|
||||
bool has_brim_auto = object->config().brim_type == btAutoBrim;
|
||||
const bool use_auto_brim_ears = object->config().brim_type == btEar;
|
||||
const bool use_brim_ears = object->config().brim_type == btPainted;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_auto_brim_ears || use_brim_ears;
|
||||
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
|
||||
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
|
||||
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
|
||||
//ORCA: Select brim base slices from EFC-compensated outline when enabled.
|
||||
const bool use_efc_outline = use_brim_efc_outline(*object);
|
||||
ExPolygons brim_slices_storage;
|
||||
const ExPolygons* brim_slices = nullptr;
|
||||
//ORCA: Select EFC-adjusted bottom outline when enabled.
|
||||
if (use_efc_outline)
|
||||
brim_slices_storage = get_print_object_bottom_layer_expolygons(*object);
|
||||
brim_slices = use_efc_outline ? &brim_slices_storage : &object->layers().front()->lslices;
|
||||
|
||||
ExPolygons brim_area_object;
|
||||
ExPolygons no_brim_area_object;
|
||||
ExPolygons brim_area_support;
|
||||
ExPolygons no_brim_area_support;
|
||||
Polygons holes_object;
|
||||
Polygons holes_support;
|
||||
if (objectWithExtruder.second == extruderNo && brimToWrite.at(object->id()).obj) {
|
||||
double adhesion = getadhesionCoeff(object);
|
||||
double maxSpeed = Model::findMaxSpeed(object->model_object());
|
||||
// BBS: brims are generated by volume groups
|
||||
for (const auto& volumeGroup : object->firstLayerObjGroups()) {
|
||||
// find volumePtrs included in this group
|
||||
std::vector<ModelVolume*> groupVolumePtrs;
|
||||
for (auto& volumeID : volumeGroup.volume_ids) {
|
||||
ModelVolume* currentModelVolumePtr = nullptr;
|
||||
//BBS: support shared object logic
|
||||
const PrintObject* shared_object = object->get_shared_object();
|
||||
if (!shared_object)
|
||||
shared_object = object;
|
||||
for (auto volumePtr : shared_object->model_object()->volumes) {
|
||||
if (volumePtr->id() == volumeID) {
|
||||
currentModelVolumePtr = volumePtr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentModelVolumePtr != nullptr) groupVolumePtrs.push_back(currentModelVolumePtr);
|
||||
}
|
||||
if (groupVolumePtrs.empty()) continue;
|
||||
double groupHeight = 0.;
|
||||
// config brim width in auto-brim mode
|
||||
if (has_brim_auto) {
|
||||
double brimWidthRaw = configBrimWidthByVolumeGroups(adhesion, maxSpeed, groupVolumePtrs, volumeGroup.slices, groupHeight);
|
||||
brim_width = scale_(floor(brimWidthRaw / flowWidth / 2) * flowWidth * 2);
|
||||
}
|
||||
ExPolygons volume_group_slices_efc;
|
||||
const ExPolygons* volume_group_slices = &volumeGroup.slices;
|
||||
if (use_efc_outline) {
|
||||
//ORCA: When using EFC outline, restrict per-volume-group slices to the
|
||||
// EFC-adjusted bottom footprint to keep brim width heuristics consistent.
|
||||
volume_group_slices_efc = intersection_ex(*brim_slices, volumeGroup.slices);
|
||||
volume_group_slices = &volume_group_slices_efc;
|
||||
}
|
||||
for (const ExPolygon& ex_poly : *volume_group_slices) {
|
||||
// BBS: additional brim width will be added if part's adhesion area is too small and brim is not generated
|
||||
float brim_width_mod;
|
||||
if (brim_width < scale_(5.) && has_brim_auto && groupHeight > 10.) {
|
||||
brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length
|
||||
&& brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width;
|
||||
}
|
||||
else {
|
||||
brim_width_mod = brim_width;
|
||||
}
|
||||
//BBS: brim width should be limited to the 1.5*boundingboxSize of a single polygon.
|
||||
if (has_brim_auto) {
|
||||
BoundingBox bbox2 = ex_poly.contour.bounding_box();
|
||||
brim_width_mod = std::min(brim_width_mod, float(std::max(bbox2.size()(0), bbox2.size()(1))));
|
||||
}
|
||||
brim_width_mod = floor(brim_width_mod / scaled_flow_width / 2) * scaled_flow_width * 2;
|
||||
|
||||
Polygons ex_poly_holes_reversed = ex_poly.holes;
|
||||
polygons_reverse(ex_poly_holes_reversed);
|
||||
|
||||
if (has_outer_brim) {
|
||||
// BBS: inner and outer boundary are offset from the same polygon incase of round off error.
|
||||
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
|
||||
ExPolygons outerExpoly;
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
|
||||
//outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width_mod - brim_offset - flow.scaled_spacing());
|
||||
outerExpoly = make_brim_ears_auto(innerExpoly, size_ear, ear_detection_length, brim_ears_max_angle, true);
|
||||
}else {
|
||||
outerExpoly = offset_ex(innerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
|
||||
}
|
||||
append(brim_area_object, diff_ex(outerExpoly, innerExpoly));
|
||||
}
|
||||
if (has_inner_brim) {
|
||||
ExPolygons outerExpoly;
|
||||
auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
|
||||
}else {
|
||||
outerExpoly = offset_ex(ex_poly_holes_reversed, -brim_offset);
|
||||
}
|
||||
append(brim_area_object, intersection_ex(diff_ex(outerExpoly, innerExpoly), ex_poly_holes_reversed));
|
||||
}
|
||||
if (!has_inner_brim) {
|
||||
// BBS: brim should be apart from holes
|
||||
append(no_brim_area_object, diff_ex(ex_poly_holes_reversed, offset_ex(ex_poly_holes_reversed, -no_brim_offset)));
|
||||
}
|
||||
if (!has_outer_brim)
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly_holes_reversed));
|
||||
append(holes_object, ex_poly_holes_reversed);
|
||||
}
|
||||
}
|
||||
auto objectIsland = offset_ex(*brim_slices, brim_offset, jtRound, SCALED_RESOLUTION);
|
||||
append(no_brim_area_object, objectIsland);
|
||||
|
||||
brimToWrite.at(object->id()).obj = false;
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
if (!brim_area_object.empty())
|
||||
append_and_translate(brim_area, brim_area_object, instance, print, brimAreaMap);
|
||||
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
||||
append_and_translate(holes, holes_object, instance);
|
||||
append_and_translate(objectIslands, objectIsland, instance);
|
||||
|
||||
}
|
||||
if (brimAreaMap.find(object->id()) != brimAreaMap.end())
|
||||
expolygons_append(brim_area, brimAreaMap[object->id()]);
|
||||
}
|
||||
support_material_extruder = object->config().support_filament;
|
||||
if (support_material_extruder == 0 && object->has_support_material()) {
|
||||
if (print.config().print_sequence == PrintSequence::ByObject)
|
||||
support_material_extruder = objectWithExtruder.second;
|
||||
else
|
||||
support_material_extruder = printExtruders.front() + 1;
|
||||
}
|
||||
if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) {
|
||||
if (!object->support_layers().empty() && object->support_layers().front()->support_type==stInnerNormal) {
|
||||
for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) {
|
||||
// Brim will not be generated for supports
|
||||
/*
|
||||
if (has_outer_brim) {
|
||||
append(brim_area_support, diff_ex(offset_ex(support_contour, brim_width + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(support_contour, brim_offset)));
|
||||
}
|
||||
if (has_inner_brim || has_outer_brim)
|
||||
append(no_brim_area_support, offset_ex(support_contour, 0));
|
||||
*/
|
||||
no_brim_area_support.emplace_back(support_contour);
|
||||
}
|
||||
}
|
||||
// BBS
|
||||
if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) {
|
||||
for (const ExPolygon &ex_poly : object->support_layers().front()->lslices) {
|
||||
// BBS: additional brim width will be added if adhesion area is too small without brim
|
||||
float brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length
|
||||
&& brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width;
|
||||
brim_width_mod = floor(brim_width_mod / scaled_flow_width / 2) * scaled_flow_width * 2;
|
||||
// Brim will not be generated for supports
|
||||
/*
|
||||
if (has_outer_brim) {
|
||||
append(brim_area_support, diff_ex(offset_ex(ex_poly.contour, brim_width_mod + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(ex_poly.contour, brim_offset)));
|
||||
}
|
||||
if (has_inner_brim)
|
||||
append(brim_area_support, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset)));
|
||||
*/
|
||||
if (!has_outer_brim)
|
||||
append(no_brim_area_support, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes));
|
||||
if (!has_inner_brim && !has_outer_brim)
|
||||
append(no_brim_area_support, offset_ex(ex_poly.holes, -no_brim_offset));
|
||||
append(holes_support, ex_poly.holes);
|
||||
if (has_inner_brim || has_outer_brim)
|
||||
append(no_brim_area_support, offset_ex(ex_poly.contour, 0));
|
||||
no_brim_area_support.emplace_back(ex_poly.contour);
|
||||
}
|
||||
}
|
||||
brimToWrite.at(object->id()).sup = false;
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
if (!brim_area_support.empty())
|
||||
append_and_translate(brim_area, brim_area_support, instance, print, supportBrimAreaMap);
|
||||
append_and_translate(no_brim_area, no_brim_area_support, instance);
|
||||
append_and_translate(holes, holes_support, instance);
|
||||
}
|
||||
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
|
||||
expolygons_append(brim_area, supportBrimAreaMap[object->id()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int extruder_nums = print.config().nozzle_diameter.values.size();
|
||||
std::vector<Polygons> extruder_unprintable_area = print.get_extruder_printable_polygons();
|
||||
// Orca: if per-extruder print area is not specified, use the whole bed as printable area for all extruders
|
||||
if (extruder_unprintable_area.empty()) {
|
||||
extruder_unprintable_area.resize(extruder_nums, Polygons{Model::getBedPolygon()});
|
||||
}
|
||||
std::vector<int> filament_map = print.get_filament_maps();
|
||||
|
||||
if (print.has_wipe_tower() && !print.get_fake_wipe_tower().outer_wall.empty()) {
|
||||
ExPolygons expolyFromLines{};
|
||||
for (auto polyline : print.get_fake_wipe_tower().outer_wall.begin()->second) {
|
||||
polyline.remove_duplicate_points();
|
||||
expolyFromLines.emplace_back(polyline.points);
|
||||
expolyFromLines.back().translate(Point(scale_(print.get_fake_wipe_tower().pos[0]), scale_(print.get_fake_wipe_tower().pos[1])));
|
||||
}
|
||||
expolygons_append(no_brim_area, expolyFromLines);
|
||||
}
|
||||
|
||||
for (const PrintObject* object : print.objects()) {
|
||||
ExPolygons extruder_no_brim_area = no_brim_area;
|
||||
auto iter = std::find_if(objPrintVec.begin(), objPrintVec.end(), [object](const std::pair<ObjectID, unsigned int>& item) {
|
||||
return item.first == object->id();
|
||||
});
|
||||
|
||||
if (iter != objPrintVec.end()) {
|
||||
int extruder_id = filament_map[iter->second - 1] - 1;
|
||||
auto bedPoly = extruder_unprintable_area[extruder_id];
|
||||
auto bedExPoly = diff_ex((offset(bedPoly, scale_(30.), jtRound, SCALED_RESOLUTION)), {bedPoly});
|
||||
if (!bedExPoly.empty()) {
|
||||
extruder_no_brim_area.push_back(bedExPoly.front());
|
||||
}
|
||||
//extruder_no_brim_area = offset2_ex(extruder_no_brim_area, scaled_flow_width, -scaled_flow_width); // connect scattered small areas to prevent generating very small brims
|
||||
|
||||
}
|
||||
|
||||
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
|
||||
brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], extruder_no_brim_area);
|
||||
}
|
||||
|
||||
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
|
||||
supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], extruder_no_brim_area);
|
||||
}
|
||||
|
||||
brim_area.clear();
|
||||
for (const PrintObject* object : print.objects()) {
|
||||
// BBS: brim should be contacted to at least one object's island or brim area
|
||||
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
|
||||
// find other objects' brim area
|
||||
ExPolygons otherExPolys;
|
||||
for (const PrintObject* otherObject : print.objects()) {
|
||||
if ((otherObject->id() != object->id()) && (brimAreaMap.find(otherObject->id()) != brimAreaMap.end())) {
|
||||
expolygons_append(otherExPolys, brimAreaMap[otherObject->id()]);
|
||||
}
|
||||
}
|
||||
|
||||
auto tempArea = brimAreaMap[object->id()];
|
||||
brimAreaMap[object->id()].clear();
|
||||
|
||||
for (int ia = 0; ia != tempArea.size(); ++ia) {
|
||||
// find this object's other brim area
|
||||
ExPolygons otherExPoly;
|
||||
for (int iao = 0; iao != tempArea.size(); ++iao)
|
||||
if (iao != ia) otherExPoly.push_back(tempArea[iao]);
|
||||
|
||||
auto offsetedTa = offset_ex(tempArea[ia], print.brim_flow().scaled_spacing() * 2, jtRound, SCALED_RESOLUTION);
|
||||
if (!intersection_ex(offsetedTa, objectIslands).empty() ||
|
||||
!intersection_ex(offsetedTa, otherExPoly).empty() ||
|
||||
!intersection_ex(offsetedTa, otherExPolys).empty())
|
||||
brimAreaMap[object->id()].push_back(tempArea[ia]);
|
||||
}
|
||||
expolygons_append(brim_area, brimAreaMap[object->id()]);
|
||||
}
|
||||
}
|
||||
return brim_area;
|
||||
}
|
||||
// Flip orientation of open polylines to minimize travel distance.
|
||||
static void optimize_polylines_by_reversing(Polylines *polylines)
|
||||
{
|
||||
for (size_t poly_idx = 1; poly_idx < polylines->size(); ++poly_idx) {
|
||||
const Polyline &prev = (*polylines)[poly_idx - 1];
|
||||
Polyline & next = (*polylines)[poly_idx];
|
||||
|
||||
if (!next.is_closed()) {
|
||||
double dist_to_start = (next.first_point() - prev.last_point()).cast<double>().norm();
|
||||
double dist_to_end = (next.last_point() - prev.last_point()).cast<double>().norm();
|
||||
|
||||
if (dist_to_end < dist_to_start)
|
||||
next.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Polylines connect_brim_lines(Polylines &&polylines, const Polygons &brim_area, float max_connection_length)
|
||||
{
|
||||
if (polylines.empty())
|
||||
return {};
|
||||
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
bbox.merge(get_extents(brim_area));
|
||||
|
||||
EdgeGrid::Grid grid(bbox.inflated(SCALED_EPSILON));
|
||||
grid.create(brim_area, polylines, coord_t(scale_(10.)));
|
||||
|
||||
struct Visitor
|
||||
{
|
||||
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix)
|
||||
{
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
this->intersect = false;
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
if (Geometry::segments_intersect(segment.first, segment.second, brim_line.a, brim_line.b)) {
|
||||
this->intersect = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
Line brim_line;
|
||||
bool intersect = false;
|
||||
|
||||
} visitor(grid);
|
||||
|
||||
// Connect successive polylines if they are open, their ends are closer than max_connection_length.
|
||||
// Remove empty polylines.
|
||||
{
|
||||
// Skip initial empty lines.
|
||||
size_t poly_idx = 0;
|
||||
for (; poly_idx < polylines.size() && polylines[poly_idx].empty(); ++ poly_idx) ;
|
||||
size_t end = ++ poly_idx;
|
||||
double max_connection_length2 = Slic3r::sqr(max_connection_length);
|
||||
for (; poly_idx < polylines.size(); ++poly_idx) {
|
||||
Polyline &next = polylines[poly_idx];
|
||||
if (! next.empty()) {
|
||||
Polyline &prev = polylines[end - 1];
|
||||
bool connect = false;
|
||||
if (! prev.is_closed() && ! next.is_closed()) {
|
||||
double dist2 = (prev.last_point() - next.first_point()).cast<double>().squaredNorm();
|
||||
if (dist2 <= max_connection_length2) {
|
||||
visitor.brim_line.a = prev.last_point();
|
||||
visitor.brim_line.b = next.first_point();
|
||||
// Shrink the connection line to avoid collisions with the brim centerlines.
|
||||
visitor.brim_line.extend(-SCALED_EPSILON);
|
||||
grid.visit_cells_intersecting_line(visitor.brim_line.a, visitor.brim_line.b, visitor);
|
||||
connect = ! visitor.intersect;
|
||||
}
|
||||
}
|
||||
if (connect) {
|
||||
append(prev.points, std::move(next.points));
|
||||
} else {
|
||||
if (end < poly_idx)
|
||||
polylines[end] = std::move(next);
|
||||
++ end;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end < polylines.size())
|
||||
polylines.erase(polylines.begin() + int(end), polylines.end());
|
||||
}
|
||||
|
||||
return std::move(polylines);
|
||||
}
|
||||
//BBS: generate out brim by offseting ExPolygons 'islands_area_ex'
|
||||
Polygons tryExPolygonOffset(const ExPolygons& islandAreaEx, const Print& print)
|
||||
{
|
||||
const auto scaled_resolution = scaled<double>(print.config().resolution.value);
|
||||
Polygons loops;
|
||||
ExPolygons islands_ex;
|
||||
Flow flow = print.brim_flow();
|
||||
|
||||
double resolution = 0.0125 / SCALING_FACTOR;
|
||||
islands_ex = islandAreaEx;
|
||||
for (ExPolygon& poly_ex : islands_ex)
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
islands_ex = offset_ex(std::move(islands_ex), -0.5f * float(flow.scaled_spacing()), jtRound, resolution);
|
||||
for (size_t i = 0; !islands_ex.empty(); ++i) {
|
||||
for (ExPolygon& poly_ex : islands_ex)
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
polygons_append(loops, to_polygons(islands_ex));
|
||||
islands_ex = offset_ex(std::move(islands_ex), -1.3f*float(flow.scaled_spacing()), jtRound, resolution);
|
||||
for (ExPolygon& poly_ex : islands_ex)
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
islands_ex = offset_ex(std::move(islands_ex), 0.3f*float(flow.scaled_spacing()), jtRound, resolution);
|
||||
}
|
||||
return loops;
|
||||
}
|
||||
//BBS: a function creates the ExtrusionEntityCollection from the brim area defined by ExPolygons
|
||||
ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area) {
|
||||
Polygons loops = tryExPolygonOffset(singleBrimArea, print);
|
||||
Flow flow = print.brim_flow();
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
|
||||
std::vector<Polylines> loops_pl_by_levels;
|
||||
{
|
||||
Polylines loops_pl = to_polylines(loops);
|
||||
loops_pl_by_levels.assign(loops_pl.size(), Polylines());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, loops_pl.size()),
|
||||
[&loops_pl_by_levels, &loops_pl /*, &islands_area*/](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
loops_pl_by_levels[i] = chain_polylines({ std::move(loops_pl[i]) });
|
||||
//loops_pl_by_levels[i] = chain_polylines(intersection_pl({ std::move(loops_pl[i]) }, islands_area));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// output
|
||||
ExtrusionEntityCollection brim;
|
||||
// Reduce down to the ordered list of polylines.
|
||||
Polylines all_loops;
|
||||
for (Polylines& polylines : loops_pl_by_levels)
|
||||
append(all_loops, std::move(polylines));
|
||||
loops_pl_by_levels.clear();
|
||||
|
||||
// Flip orientation of open polylines to minimize travel distance.
|
||||
optimize_polylines_by_reversing(&all_loops);
|
||||
all_loops = connect_brim_lines(std::move(all_loops), offset(singleBrimArea, float(SCALED_EPSILON)), float(flow.scaled_spacing()) * 2.f);
|
||||
|
||||
//BBS: finally apply the plate offset which may very large
|
||||
auto plate_offset = print.get_plate_origin();
|
||||
Point scaled_plate_offset = Point(scaled(plate_offset.x()), scaled(plate_offset.y()));
|
||||
for (Polyline& one_loop : all_loops)
|
||||
one_loop.translate(scaled_plate_offset);
|
||||
|
||||
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erBrim, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
|
||||
return brim;
|
||||
}
|
||||
|
||||
//BBS: an overload of the orignal brim generator that generates the brim by obj and by extruders
|
||||
void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders)
|
||||
{
|
||||
std::map<ObjectID, double> brim_width_map;
|
||||
std::map<ObjectID, ExPolygons> brimAreaMap;
|
||||
std::map<ObjectID, ExPolygons> supportBrimAreaMap;
|
||||
Flow flow = print.brim_flow();
|
||||
const auto scaled_resolution = scaled<double>(print.config().resolution.value);
|
||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||
float(flow.scaled_spacing()), brimAreaMap, supportBrimAreaMap, objPrintVec, printExtruders);
|
||||
|
||||
// BBS: Find boundingbox of the first layer
|
||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||
BoundingBox bbx;
|
||||
PrintObject* object = const_cast<PrintObject*>(print.get_object(printObjID));
|
||||
//ORCA: Use EFC-compensated outline for brim bounding box when enabled.
|
||||
const ExPolygons brim_slices = use_brim_efc_outline(*object) ?
|
||||
get_print_object_bottom_layer_expolygons(*object) : object->layers().front()->lslices;
|
||||
for (const ExPolygon& ex_poly : brim_slices)
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
auto ex_poly_translated = ex_poly;
|
||||
ex_poly_translated.translate(instance.shift_without_plate_offset());
|
||||
bbx.merge(get_extents(ex_poly_translated.contour));
|
||||
}
|
||||
if (!object->support_layers().empty())
|
||||
for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing())
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
auto ex_poly_translated = support_contour;
|
||||
ex_poly_translated.translate(instance.shift_without_plate_offset());
|
||||
bbx.merge(get_extents(ex_poly_translated));
|
||||
}
|
||||
if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) {
|
||||
for (const ExPolygon& ex_poly : supportBrimAreaMap.at(printObjID))
|
||||
bbx.merge(get_extents(ex_poly.contour));
|
||||
}
|
||||
if (brimAreaMap.find(printObjID) != brimAreaMap.end()) {
|
||||
for (const ExPolygon& ex_poly : brimAreaMap.at(printObjID))
|
||||
bbx.merge(get_extents(ex_poly.contour));
|
||||
}
|
||||
object->firstLayerObjectBrimBoundingBox = bbx;
|
||||
}
|
||||
|
||||
islands_area = to_polygons(islands_area_ex);
|
||||
|
||||
// BBS: plate offset is applied
|
||||
const Vec3d plate_offset = print.get_plate_origin();
|
||||
Point plate_shift = Point(scaled(plate_offset.x()), scaled(plate_offset.y()));
|
||||
for (size_t iia = 0; iia < islands_area.size(); ++iia)
|
||||
islands_area[iia].translate(plate_shift);
|
||||
|
||||
const bool combine_brims = print.config().combine_brims.value;
|
||||
const bool is_by_object = (print.config().print_sequence == PrintSequence::ByObject);
|
||||
const bool can_combine_brims = combine_brims && !is_by_object;
|
||||
|
||||
if (!can_combine_brims) {
|
||||
// Orca: Generate brims separately for each object when multiple extruders are used
|
||||
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
|
||||
};
|
||||
}
|
||||
for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Orca: Unified brim mode (non-sequential printing)
|
||||
ExPolygons all_brims_merged;
|
||||
std::vector<ObjectID> brim_object_ids;
|
||||
|
||||
// Add all object brims
|
||||
for (auto& [obj_id, brims] : brimAreaMap) {
|
||||
if (!brims.empty()) {
|
||||
expolygons_append(all_brims_merged, brims);
|
||||
brim_object_ids.push_back(obj_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!all_brims_merged.empty()) {
|
||||
// Merge all brims into a single continuous area
|
||||
all_brims_merged = union_ex(all_brims_merged);
|
||||
|
||||
// Apply a tiny morphological cleanup to reduce boolean-union micro-artifacts.
|
||||
const float brim_cleanup_delta = std::max(float(scaled_resolution), float(SCALED_EPSILON));
|
||||
all_brims_merged = offset2_ex(all_brims_merged, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
|
||||
|
||||
// Generate infill once for the merged brim area.
|
||||
ExtrusionEntityCollection merged_brim = makeBrimInfill(all_brims_merged, print, islands_area);
|
||||
|
||||
// In unified mode, assign the merged brim to a deterministic carrier object.
|
||||
// Pick the first object in print order that actually contributed brim area.
|
||||
ObjectID carrier_id;
|
||||
bool carrier_found = false;
|
||||
for (const auto& [obj_id, _extruder] : objPrintVec) {
|
||||
if (std::find(brim_object_ids.begin(), brim_object_ids.end(), obj_id) != brim_object_ids.end()) {
|
||||
carrier_id = obj_id;
|
||||
carrier_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!carrier_found)
|
||||
carrier_id = brim_object_ids.front();
|
||||
|
||||
brimMap[carrier_id] = std::move(merged_brim);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef slic3r_Brim_hpp_
|
||||
#define slic3r_Brim_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
|
||||
#include<map>
|
||||
#include<vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class ExtrusionEntityCollection;
|
||||
class PrintTryCancel;
|
||||
class ObjectID;
|
||||
|
||||
// Produce brim lines around those objects, that have the brim enabled.
|
||||
// Collect islands_area to be merged into the final 1st layer convex hull.
|
||||
void make_brim(const Print& print, PrintTryCancel try_cancel,
|
||||
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders);
|
||||
|
||||
// BBS: automatically make brim
|
||||
ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area);
|
||||
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_Brim_hpp_
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef BRIMEARSPOINT_HPP
|
||||
#define BRIMEARSPOINT_HPP
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// An enum to keep track of where the current points on the ModelObject came from.
|
||||
enum class PointsStatus {
|
||||
NoPoints, // No points were generated so far.
|
||||
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
||||
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
||||
UserModified // User has done some edits.
|
||||
};
|
||||
|
||||
struct BrimPoint
|
||||
{
|
||||
Vec3f pos;
|
||||
float head_front_radius;
|
||||
|
||||
BrimPoint()
|
||||
: pos(Vec3f::Zero()), head_front_radius(0.f)
|
||||
{}
|
||||
|
||||
BrimPoint(float pos_x,
|
||||
float pos_y,
|
||||
float pos_z,
|
||||
float head_radius)
|
||||
: pos(pos_x, pos_y, pos_z)
|
||||
, head_front_radius(head_radius)
|
||||
{}
|
||||
|
||||
BrimPoint(Vec3f position, float head_radius)
|
||||
: pos(position)
|
||||
, head_front_radius(head_radius)
|
||||
{}
|
||||
|
||||
Vec3f transform(const Transform3d &trsf)
|
||||
{
|
||||
Vec3d result = trsf * pos.cast<double>();
|
||||
return result.cast<float>();
|
||||
}
|
||||
|
||||
void set_transform(const Transform3d& trsf)
|
||||
{
|
||||
pos = transform(trsf);
|
||||
}
|
||||
|
||||
bool operator==(const BrimPoint &sp) const
|
||||
{
|
||||
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
|
||||
return (pos == sp.pos) && rdiff < float(EPSILON);
|
||||
}
|
||||
|
||||
bool operator!=(const BrimPoint &sp) const { return !(sp == (*this)); }
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, head_front_radius);
|
||||
}
|
||||
};
|
||||
|
||||
using BrimPoints = std::vector<BrimPoint>;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // BRIMEARSPOINT_HPP
|
||||
@@ -0,0 +1,594 @@
|
||||
#include "BuildVolume.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BuildVolume::BuildVolume(const std::vector<Vec2d> &printable_area, const double printable_height, const std::vector<std::vector<Vec2d>> &extruder_areas, const std::vector<double>& extruder_printable_heights)
|
||||
: m_bed_shape(printable_area), m_max_print_height(printable_height), m_extruder_shapes(extruder_areas), m_extruder_printable_height(extruder_printable_heights)
|
||||
{
|
||||
assert(printable_height >= 0);
|
||||
//assert(extruder_printable_heights.size() == extruder_areas.size());
|
||||
|
||||
m_polygon = Polygon::new_scale(printable_area);
|
||||
assert(m_polygon.is_counter_clockwise());
|
||||
|
||||
// Calcuate various metrics of the input polygon.
|
||||
m_convex_hull = Geometry::convex_hull(m_polygon.points);
|
||||
m_bbox = get_extents(m_convex_hull);
|
||||
m_area = m_polygon.area();
|
||||
|
||||
BoundingBoxf bboxf = get_extents(printable_area);
|
||||
m_bboxf = BoundingBoxf3{ to_3d(bboxf.min, 0.), to_3d(bboxf.max, printable_height) };
|
||||
|
||||
if (printable_area.size() >= 4 && std::abs((m_area - double(m_bbox.size().x()) * double(m_bbox.size().y()))) < sqr(SCALED_EPSILON)) {
|
||||
// Square print bed, use the bounding box for collision detection.
|
||||
m_type = BuildVolume_Type::Rectangle;
|
||||
m_circle.center = 0.5 * (m_bbox.min.cast<double>() + m_bbox.max.cast<double>());
|
||||
m_circle.radius = 0.5 * m_bbox.size().cast<double>().norm();
|
||||
} else if (printable_area.size() > 3) {
|
||||
// Circle was discretized, formatted into text with limited accuracy, thus the circle was deformed.
|
||||
// RANSAC is slightly more accurate than the iterative Taubin / Newton method with such an input.
|
||||
// m_circle = Geometry::circle_taubin_newton(printable_area);
|
||||
m_circle = Geometry::circle_ransac(printable_area);
|
||||
bool is_circle = true;
|
||||
#ifndef NDEBUG
|
||||
// Measuring maximum absolute error of interpolating an input polygon with circle.
|
||||
double max_error = 0;
|
||||
#endif // NDEBUG
|
||||
Vec2d prev = printable_area.back();
|
||||
for (const Vec2d &p : printable_area) {
|
||||
#ifndef NDEBUG
|
||||
max_error = std::max(max_error, std::abs((p - m_circle.center).norm() - m_circle.radius));
|
||||
#endif // NDEBUG
|
||||
if (// Polygon vertices must lie very close the circle.
|
||||
std::abs((p - m_circle.center).norm() - m_circle.radius) > 0.005 ||
|
||||
// Midpoints of polygon edges must not undercat more than 3mm. This corresponds to 72 edges per circle generated by BedShapePanel::update_shape().
|
||||
m_circle.radius - (0.5 * (prev + p) - m_circle.center).norm() > 3.) {
|
||||
is_circle = false;
|
||||
break;
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
if (is_circle) {
|
||||
m_type = BuildVolume_Type::Circle;
|
||||
m_circle.center = scaled<double>(m_circle.center);
|
||||
m_circle.radius = scaled<double>(m_circle.radius);
|
||||
}
|
||||
}
|
||||
|
||||
if (printable_area.size() >= 3 && m_type == BuildVolume_Type::Invalid) {
|
||||
// Circle check is not used for Convex / Custom shapes, fill it with something reasonable.
|
||||
m_circle = Geometry::smallest_enclosing_circle_welzl(m_convex_hull.points);
|
||||
m_type = (m_convex_hull.area() - m_area) < sqr(SCALED_EPSILON) ? BuildVolume_Type::Convex : BuildVolume_Type::Custom;
|
||||
// Initialize the top / bottom decomposition for inside convex polygon check. Do it with two different epsilons applied.
|
||||
auto convex_decomposition = [](const Polygon &in, double epsilon) {
|
||||
Polygon src = expand(in, float(epsilon)).front();
|
||||
std::vector<Vec2d> pts;
|
||||
pts.reserve(src.size());
|
||||
for (const Point &pt : src.points)
|
||||
pts.emplace_back(unscaled<double>(pt.cast<double>().eval()));
|
||||
return Geometry::decompose_convex_polygon_top_bottom(pts);
|
||||
};
|
||||
m_top_bottom_convex_hull_decomposition_scene = convex_decomposition(m_convex_hull, SceneEpsilon);
|
||||
m_top_bottom_convex_hull_decomposition_bed = convex_decomposition(m_convex_hull, BedEpsilon);
|
||||
}
|
||||
|
||||
if (m_extruder_shapes.size() > 0)
|
||||
{
|
||||
m_shared_volume.data[0] = m_bboxf.min.x();
|
||||
m_shared_volume.data[1] = m_bboxf.min.y();
|
||||
m_shared_volume.data[2] = m_bboxf.max.x();
|
||||
m_shared_volume.data[3] = m_bboxf.max.y();
|
||||
m_shared_volume.zs[1] = m_bboxf.max.z();
|
||||
for (unsigned int index = 0; index < m_extruder_shapes.size(); index++)
|
||||
{
|
||||
std::vector<Vec2d>& extruder_shape = m_extruder_shapes[index];
|
||||
BuildExtruderVolume extruder_volume;
|
||||
|
||||
if (extruder_shape.empty())
|
||||
{
|
||||
//should not happen
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("Found invalid extruder_printable_area of index %1%")%index;
|
||||
assert(false);
|
||||
m_extruder_shapes.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((extruder_shape == printable_area)&&(extruder_printable_heights[index] == printable_height)) {
|
||||
extruder_volume.same_with_bed = true;
|
||||
extruder_volume.type = m_type;
|
||||
extruder_volume.bbox = m_bbox;
|
||||
extruder_volume.bboxf = m_bboxf;
|
||||
extruder_volume.circle = m_circle;
|
||||
}
|
||||
else {
|
||||
Polygon poly = Polygon::new_scale(extruder_shape);
|
||||
|
||||
double poly_area = poly.area();
|
||||
extruder_volume.bbox = get_extents(poly);
|
||||
BoundingBoxf temp_bboxf = get_extents(extruder_shape);
|
||||
extruder_volume.bboxf = BoundingBoxf3{ to_3d(temp_bboxf.min, 0.), to_3d(temp_bboxf.max, extruder_printable_heights[index]) };
|
||||
|
||||
if (extruder_shape.size() >= 4 && std::abs((poly_area - double(extruder_volume.bbox.size().x()) * double(extruder_volume.bbox.size().y()))) < sqr(SCALED_EPSILON))
|
||||
{
|
||||
extruder_volume.type = BuildVolume_Type::Rectangle;
|
||||
extruder_volume.circle.center = 0.5 * (extruder_volume.bbox.min.cast<double>() + extruder_volume.bbox.max.cast<double>());
|
||||
extruder_volume.circle.radius = 0.5 * extruder_volume.bbox.size().cast<double>().norm();
|
||||
}
|
||||
else if (extruder_shape.size() > 3) {
|
||||
extruder_volume.circle = Geometry::circle_ransac(extruder_shape);
|
||||
bool is_circle = true;
|
||||
|
||||
Vec2d prev = extruder_shape.back();
|
||||
for (const Vec2d &p : extruder_shape) {
|
||||
if (// Polygon vertices must lie very close the circle.
|
||||
std::abs((p - extruder_volume.circle.center).norm() - extruder_volume.circle.radius) > 0.005 ||
|
||||
// Midpoints of polygon edges must not undercat more than 3mm. This corresponds to 72 edges per circle generated by BedShapePanel::update_shape().
|
||||
extruder_volume.circle.radius - (0.5 * (prev + p) -extruder_volume.circle.center).norm() > 3.) {
|
||||
is_circle = false;
|
||||
break;
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
if (is_circle) {
|
||||
extruder_volume.type = BuildVolume_Type::Circle;
|
||||
extruder_volume.circle.center = scaled<double>(extruder_volume.circle.center);
|
||||
extruder_volume.circle.radius = scaled<double>(extruder_volume.circle.radius);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_type == BuildVolume_Type::Invalid) {
|
||||
//not supported currently, use the same as bed
|
||||
extruder_volume.same_with_bed = true;
|
||||
extruder_volume.type = m_type;
|
||||
extruder_volume.bbox = m_bbox;
|
||||
extruder_volume.bboxf = m_bboxf;
|
||||
extruder_volume.circle = m_circle;
|
||||
}
|
||||
//always ignore z
|
||||
extruder_volume.bboxf.min.z() = -std::numeric_limits<double>::max();
|
||||
}
|
||||
m_extruder_volumes.push_back(std::move(extruder_volume));
|
||||
|
||||
if (m_shared_volume.data[0] < extruder_volume.bboxf.min.x())
|
||||
m_shared_volume.data[0] = extruder_volume.bboxf.min.x();
|
||||
if (m_shared_volume.data[1] < extruder_volume.bboxf.min.y())
|
||||
m_shared_volume.data[1] = extruder_volume.bboxf.min.y();
|
||||
if (m_shared_volume.data[2] > extruder_volume.bboxf.max.x())
|
||||
m_shared_volume.data[2] = extruder_volume.bboxf.max.x();
|
||||
if (m_shared_volume.data[3] > extruder_volume.bboxf.max.y())
|
||||
m_shared_volume.data[3] = extruder_volume.bboxf.max.y();
|
||||
if (m_shared_volume.zs[1] > extruder_volume.bboxf.max.z())
|
||||
m_shared_volume.zs[1] = extruder_volume.bboxf.max.z();
|
||||
}
|
||||
|
||||
m_shared_volume.type = static_cast<int>(m_type);
|
||||
m_shared_volume.zs[0] = 0.f;
|
||||
//m_shared_volume.zs[1] = printable_height;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "BuildVolume printable_area clasified as: " << this->type_name();
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Tests intersections of projected triangles, not just their vertices against a bounding box.
|
||||
// This test also correctly evaluates collision of a non-convex object with the bounding box.
|
||||
// Not used, slower than simple bounding box collision check and nobody complained about the inaccuracy of the simple test.
|
||||
static inline BuildVolume::ObjectState rectangle_test(const indexed_triangle_set &its, const Transform3f &trafo, const Vec2f min, const Vec2f max, const float max_z)
|
||||
{
|
||||
bool inside = false;
|
||||
bool outside = false;
|
||||
|
||||
auto sign = [](const Vec3f& pt) -> char { return pt.z() > 0 ? 1 : pt.z() < 0 ? -1 : 0; };
|
||||
|
||||
// Returns true if both inside and outside are set, thus early exit.
|
||||
auto test_intersection = [&inside, &outside, min, max, max_z](const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) -> bool {
|
||||
// First test whether the triangle is completely inside or outside the bounding box.
|
||||
Vec3f pmin = p1.cwiseMin(p2).cwiseMin(p3);
|
||||
Vec3f pmax = p1.cwiseMax(p2).cwiseMax(p3);
|
||||
bool tri_inside = false;
|
||||
bool tri_outside = false;
|
||||
if (pmax.x() < min.x() || pmin.x() > max.x() || pmax.y() < min.y() || pmin.y() > max.y()) {
|
||||
// Separated by one of the rectangle sides.
|
||||
tri_outside = true;
|
||||
} else if (pmin.x() >= min.x() && pmax.x() <= max.x() && pmin.y() >= min.y() && pmax.y() <= max.y()) {
|
||||
// Fully inside the rectangle.
|
||||
tri_inside = true;
|
||||
} else {
|
||||
// Bounding boxes overlap. Test triangle sides against the bbox corners.
|
||||
Vec2f v1(- p2.y() + p1.y(), p2.x() - p1.x());
|
||||
Vec2f v2(- p2.y() + p2.y(), p3.x() - p2.x());
|
||||
Vec2f v3(- p1.y() + p3.y(), p1.x() - p3.x());
|
||||
bool ccw = cross2(v1, v2) > 0;
|
||||
for (const Vec2f &p : { Vec2f{ min.x(), min.y() }, Vec2f{ min.x(), max.y() }, Vec2f{ max.x(), min.y() }, Vec2f{ max.x(), max.y() } }) {
|
||||
auto dot = v1.dot(p);
|
||||
if (ccw ? dot >= 0 : dot <= 0)
|
||||
tri_inside = true;
|
||||
else
|
||||
tri_outside = true;
|
||||
}
|
||||
}
|
||||
inside |= tri_inside;
|
||||
outside |= tri_outside;
|
||||
return inside && outside;
|
||||
};
|
||||
|
||||
// Edge crosses the z plane. Calculate intersection point with the plane.
|
||||
auto clip_edge = [](const Vec3f &p1, const Vec3f &p2) -> Vec3f {
|
||||
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
||||
return { p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z };
|
||||
};
|
||||
|
||||
// Clip at (p1, p2), p3 must be on the clipping plane.
|
||||
// Returns true if both inside and outside are set, thus early exit.
|
||||
auto clip_and_test1 = [&test_intersection, &clip_edge](const Vec3f &p1, const Vec3f &p2, const Vec3f &p3, bool p1above) -> bool {
|
||||
Vec3f pa = clip_edge(p1, p2);
|
||||
return p1above ? test_intersection(p1, pa, p3) : test_intersection(pa, p2, p3);
|
||||
};
|
||||
|
||||
// Clip at (p1, p2) and (p2, p3).
|
||||
// Returns true if both inside and outside are set, thus early exit.
|
||||
auto clip_and_test2 = [&test_intersection, &clip_edge](const Vec3f &p1, const Vec3f &p2, const Vec3f &p3, bool p2above) -> bool {
|
||||
Vec3f pa = clip_edge(p1, p2);
|
||||
Vec3f pb = clip_edge(p2, p3);
|
||||
return p2above ? test_intersection(pa, p2, pb) : test_intersection(p1, pa, p3) || test_intersection(p3, pa, pb);
|
||||
};
|
||||
|
||||
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||
const Vec3f pts[3] = { trafo * its.vertices[tri(0)], trafo * its.vertices[tri(1)], trafo * its.vertices[tri(2)] };
|
||||
char signs[3] = { sign(pts[0]), sign(pts[1]), sign(pts[2]) };
|
||||
bool clips[3] = { signs[0] * signs[1] == -1, signs[1] * signs[2] == -1, signs[2] * signs[0] == -1 };
|
||||
if (clips[0]) {
|
||||
if (clips[1]) {
|
||||
// Clipping at (pt0, pt1) and (pt1, pt2).
|
||||
if (clip_and_test2(pts[0], pts[1], pts[2], signs[1] > 0))
|
||||
break;
|
||||
} else if (clips[2]) {
|
||||
// Clipping at (pt0, pt1) and (pt0, pt2).
|
||||
if (clip_and_test2(pts[2], pts[0], pts[1], signs[0] > 0))
|
||||
break;
|
||||
} else {
|
||||
// Clipping at (pt0, pt1), pt2 must be on the clipping plane.
|
||||
if (clip_and_test1(pts[0], pts[1], pts[2], signs[0] > 0))
|
||||
break;
|
||||
}
|
||||
} else if (clips[1]) {
|
||||
if (clips[2]) {
|
||||
// Clipping at (pt1, pt2) and (pt0, pt2).
|
||||
if (clip_and_test2(pts[0], pts[1], pts[2], signs[1] > 0))
|
||||
break;
|
||||
} else {
|
||||
// Clipping at (pt1, pt2), pt0 must be on the clipping plane.
|
||||
if (clip_and_test1(pts[1], pts[2], pts[0], signs[1] > 0))
|
||||
break;
|
||||
}
|
||||
} else if (clips[2]) {
|
||||
// Clipping at (pt0, pt2), pt1 must be on the clipping plane.
|
||||
if (clip_and_test1(pts[2], pts[0], pts[1], signs[2] > 0))
|
||||
break;
|
||||
} else if (signs[0] >= 0 && signs[1] >= 0 && signs[2] >= 0) {
|
||||
// The triangle is above or on the clipping plane.
|
||||
if (test_intersection(pts[0], pts[1], pts[2]))
|
||||
break;
|
||||
}
|
||||
}
|
||||
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Trim the input transformed triangle mesh with print bed and test the remaining vertices with is_inside callback.
|
||||
// Return inside / colliding / outside state.
|
||||
template<typename InsideFn>
|
||||
BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, bool convex, InsideFn is_inside)
|
||||
{
|
||||
size_t num_inside = 0;
|
||||
size_t num_above = 0;
|
||||
bool inside = false;
|
||||
bool outside = false;
|
||||
static constexpr const auto world_min_z = float(-BuildVolume::SceneEpsilon);
|
||||
|
||||
if (may_be_below_bed)
|
||||
{
|
||||
// Slower test, needs to clip the object edges with the print bed plane.
|
||||
// 1) Allocate transformed vertices with their position with respect to print bed surface.
|
||||
std::vector<char> sides;
|
||||
sides.reserve(its.vertices.size());
|
||||
|
||||
const auto sign = [](const stl_vertex& pt) { return pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0; };
|
||||
|
||||
bool below_outside = false;
|
||||
|
||||
for (const stl_vertex &v : its.vertices) {
|
||||
stl_vertex pt = trafo * v;
|
||||
const int s = sign(pt);
|
||||
sides.emplace_back(s);
|
||||
if (s >= 0) {
|
||||
// Vertex above or on print bed surface. Test whether it is inside the build volume.
|
||||
++ num_above;
|
||||
if (is_inside(pt))
|
||||
++ num_inside;
|
||||
} else if (convex && !below_outside) {
|
||||
pt.z() = 0;
|
||||
if (!is_inside(pt))
|
||||
below_outside = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_above == 0)
|
||||
// Special case, the object is completely below the print bed, thus it is outside,
|
||||
// however we want to allow an object to be still printable if some of its parts are completely below the print bed.
|
||||
return BuildVolume::ObjectState::Below;
|
||||
|
||||
// 2) Calculate intersections of triangle edges with the build surface.
|
||||
inside = num_inside > 0;
|
||||
outside = num_inside < num_above;
|
||||
// Orca: for convex shape, if everything inside then don't bother check intersection
|
||||
if (num_above < its.vertices.size() && !(inside && outside) && (!(inside && !below_outside) || !convex)) {
|
||||
// Not completely above the build surface and status may still change by testing edges intersecting the build platform.
|
||||
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||
const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] };
|
||||
if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) {
|
||||
// Some edge of this triangle intersects the build platform. Calculate the intersection.
|
||||
int iprev = 2;
|
||||
for (int iedge = 0; iedge < 3; ++ iedge) {
|
||||
if (s[iprev] * s[iedge] == -1) {
|
||||
// edge intersects the build surface. Calculate intersection point.
|
||||
const stl_vertex p1 = trafo * its.vertices[tri(iprev)];
|
||||
const stl_vertex p2 = trafo * its.vertices[tri(iedge)];
|
||||
assert(sign(p1) == s[iprev]);
|
||||
assert(sign(p2) == s[iedge]);
|
||||
assert(p1.z() * p2.z() < 0);
|
||||
// Edge crosses the z plane. Calculate intersection point with the plane.
|
||||
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
||||
(is_inside(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)) ? inside : outside) = true;
|
||||
}
|
||||
iprev = iedge;
|
||||
}
|
||||
if (inside && outside)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Much simpler and faster code, not clipping the object with the print bed.
|
||||
assert(! may_be_below_bed);
|
||||
num_above = its.vertices.size();
|
||||
for (const stl_vertex &v : its.vertices) {
|
||||
const stl_vertex pt = trafo * v;
|
||||
assert(pt.z() >= world_min_z);
|
||||
if (is_inside(pt))
|
||||
++ num_inside;
|
||||
}
|
||||
inside = num_inside > 0;
|
||||
outside = num_inside < num_above;
|
||||
}
|
||||
|
||||
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom) const
|
||||
{
|
||||
switch (m_type) {
|
||||
case BuildVolume_Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
||||
//FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||
return object_state_templ(its, trafo, may_be_below_bed, true, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); });
|
||||
}
|
||||
case BuildVolume_Type::Circle:
|
||||
{
|
||||
Geometry::Circlef circle { unscaled<float>(m_circle.center), unscaled<float>(m_circle.radius + SceneEpsilon) };
|
||||
return m_max_print_height == 0.0 ?
|
||||
object_state_templ(its, trafo, may_be_below_bed, true, [circle](const Vec3f& pt) { return circle.contains(to_2d(pt)); }) :
|
||||
object_state_templ(its, trafo, may_be_below_bed, true, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
|
||||
}
|
||||
case BuildVolume_Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case BuildVolume_Type::Custom:
|
||||
return m_max_print_height == 0.0 ?
|
||||
object_state_templ(its, trafo, may_be_below_bed, m_type == BuildVolume_Type::Convex, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
|
||||
object_state_templ(its, trafo, may_be_below_bed, m_type == BuildVolume_Type::Convex, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
|
||||
case BuildVolume_Type::Invalid:
|
||||
default:
|
||||
return ObjectState::Inside;
|
||||
}
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const
|
||||
{
|
||||
assert(m_type == BuildVolume_Type::Rectangle);
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below :
|
||||
build_volume.contains(volume_bbox) ? ObjectState::Inside :
|
||||
build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
|
||||
}
|
||||
|
||||
const BuildVolume::BuildExtruderVolume& BuildVolume::get_extruder_area_volume(int index) const
|
||||
{
|
||||
assert(index >= 0 && index < m_extruder_volumes.size());
|
||||
return m_extruder_volumes[index];
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::check_object_state_with_extruder_area(const indexed_triangle_set &its, const Transform3f &trafo, int index) const
|
||||
{
|
||||
const BuildExtruderVolume& extruder_volume = get_extruder_area_volume(index);
|
||||
ObjectState return_state = ObjectState::Inside;
|
||||
|
||||
if (!extruder_volume.same_with_bed) {
|
||||
switch (extruder_volume.type) {
|
||||
case BuildVolume_Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = extruder_volume.bboxf.inflated(SceneEpsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||
|
||||
return_state = object_state_templ(its, trafo, false, true, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); });
|
||||
break;
|
||||
}
|
||||
case BuildVolume_Type::Circle:
|
||||
{
|
||||
Geometry::Circlef circle { unscaled<float>(extruder_volume.circle.center), unscaled<float>(extruder_volume.circle.radius + SceneEpsilon) };
|
||||
return_state = (m_max_print_height == 0.0) ?
|
||||
object_state_templ(its, trafo, false, true, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) :
|
||||
object_state_templ(its, trafo, false, true, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
|
||||
break;
|
||||
}
|
||||
case BuildVolume_Type::Invalid:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (return_state != ObjectState::Inside)
|
||||
return_state = ObjectState::Limited;
|
||||
|
||||
return return_state;
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::check_object_state_with_extruder_areas(const indexed_triangle_set &its, const Transform3f &trafo, std::vector<bool>& inside_extruders) const
|
||||
{
|
||||
ObjectState result = ObjectState::Inside;
|
||||
int extruder_area_count = get_extruder_area_count();
|
||||
inside_extruders.resize(extruder_area_count, true);
|
||||
for (int index = 0; index < extruder_area_count; index++)
|
||||
{
|
||||
ObjectState state = check_object_state_with_extruder_area(its, trafo, index);
|
||||
|
||||
if (state == ObjectState::Limited) {
|
||||
inside_extruders[index] = false;
|
||||
result = ObjectState::Limited;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::check_volume_bbox_state_with_extruder_area(const BoundingBoxf3& volume_bbox, int index) const
|
||||
{
|
||||
const BuildExtruderVolume& extruder_volume = get_extruder_area_volume(index);
|
||||
BoundingBox3Base<Vec3d> extruder_bbox = extruder_volume.bboxf.inflated(SceneEpsilon);
|
||||
if (extruder_volume.same_with_bed || extruder_bbox.contains(volume_bbox))
|
||||
return ObjectState::Inside;
|
||||
else
|
||||
return ObjectState::Limited;
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::check_volume_bbox_state_with_extruder_areas(const BoundingBoxf3& volume_bbox, std::vector<bool>& inside_extruders) const
|
||||
{
|
||||
ObjectState result = ObjectState::Inside;
|
||||
int extruder_area_count = get_extruder_area_count();
|
||||
inside_extruders.resize(extruder_area_count, true);
|
||||
for (int index = 0; index < extruder_area_count; index++)
|
||||
{
|
||||
ObjectState state = check_volume_bbox_state_with_extruder_area(volume_bbox, index);
|
||||
|
||||
if (state == ObjectState::Limited) {
|
||||
inside_extruders[index] = false;
|
||||
result = ObjectState::Limited;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const
|
||||
{
|
||||
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) {
|
||||
return move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.f && move.height != 0.f;
|
||||
};
|
||||
static constexpr const double epsilon = BedEpsilon;
|
||||
|
||||
switch (m_type) {
|
||||
case BuildVolume_Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(epsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox);
|
||||
}
|
||||
case BuildVolume_Type::Circle:
|
||||
{
|
||||
const Vec2f c = unscaled<float>(m_circle.center);
|
||||
const float r = unscaled<double>(m_circle.radius) + epsilon;
|
||||
const float r2 = sqr(r);
|
||||
return m_max_print_height == 0.0 ?
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) :
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move)
|
||||
{ return ! move_valid(move) || ((to_2d(move.position) - c).squaredNorm() <= r2 && move.position.z() <= z); });
|
||||
}
|
||||
case BuildVolume_Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case BuildVolume_Type::Custom:
|
||||
return m_max_print_height == 0.0 ?
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()); }) :
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || (Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()) && move.position.z() <= z); });
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
inline bool all_inside_vertices_normals_interleaved(const std::vector<float> &paths, Fn fn)
|
||||
{
|
||||
for (auto it = paths.begin(); it != paths.end(); ) {
|
||||
it += 3;
|
||||
if (! fn({ *it, *(it + 1), *(it + 2) }))
|
||||
return false;
|
||||
it += 3;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string_view BuildVolume::type_name(BuildVolume_Type type)
|
||||
{
|
||||
using namespace std::literals;
|
||||
switch (type) {
|
||||
case BuildVolume_Type::Invalid: return "Invalid"sv;
|
||||
case BuildVolume_Type::Rectangle: return "Rectangle"sv;
|
||||
case BuildVolume_Type::Circle: return "Circle"sv;
|
||||
case BuildVolume_Type::Convex: return "Convex"sv;
|
||||
case BuildVolume_Type::Custom: return "Custom"sv;
|
||||
}
|
||||
// make visual studio happy
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
indexed_triangle_set BuildVolume::bounding_mesh(bool scale) const
|
||||
{
|
||||
auto max_pt3 = m_bboxf.max;
|
||||
if (scale) {
|
||||
return its_make_cube(scale_(max_pt3.x()), scale_(max_pt3.y()), scale_(max_pt3.z()));
|
||||
}
|
||||
else {
|
||||
return its_make_cube(max_pt3.x(), max_pt3.y(), max_pt3.z());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,166 @@
|
||||
#ifndef slic3r_BuildVolume_hpp_
|
||||
#define slic3r_BuildVolume_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Geometry/Circle.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include <admesh/stl.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct GCodeProcessorResult;
|
||||
enum class BuildVolume_Type : char {
|
||||
// Not set yet or undefined.
|
||||
Invalid = -1,
|
||||
// Rectangular print bed. Most common, cheap to work with.
|
||||
Rectangle,
|
||||
// Circular print bed. Common on detals, cheap to work with.
|
||||
Circle,
|
||||
// Convex print bed. Complex to process.
|
||||
Convex,
|
||||
// Some non convex shape.
|
||||
Custom
|
||||
};
|
||||
|
||||
// For collision detection of objects and G-code (extrusion paths) against the build volume.
|
||||
class BuildVolume
|
||||
{
|
||||
public:
|
||||
|
||||
struct BuildExtruderVolume {
|
||||
bool same_with_bed{false};
|
||||
BuildVolume_Type type{BuildVolume_Type::Invalid};
|
||||
BoundingBox bbox;
|
||||
BoundingBoxf3 bboxf;
|
||||
Geometry::Circled circle;
|
||||
};
|
||||
|
||||
struct BuildSharedVolume
|
||||
{
|
||||
// see: Bed3D::EShapeType
|
||||
int type{ 0 };
|
||||
// data contains:
|
||||
// Rectangle:
|
||||
// [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y
|
||||
// Circle:
|
||||
// [0] = center.x, [1] = center.y, [3] = radius
|
||||
std::array<float, 4> data;
|
||||
// [0] = min z, [1] = max z
|
||||
std::array<float, 2> zs;
|
||||
};
|
||||
|
||||
// Initialized to empty, all zeros, Invalid.
|
||||
BuildVolume() {}
|
||||
// Initialize from PrintConfig::printable_area and PrintConfig::printable_height
|
||||
BuildVolume(const std::vector<Vec2d> &printable_area, const double printable_height, const std::vector<std::vector<Vec2d>> &extruder_areas, const std::vector<double>& extruder_printable_heights);
|
||||
|
||||
// Source data, unscaled coordinates.
|
||||
const std::vector<Vec2d>& printable_area() const { return m_bed_shape; }
|
||||
double printable_height() const { return m_max_print_height; }
|
||||
const std::vector<std::vector<Vec2d>>& extruder_areas() const { return m_extruder_shapes; }
|
||||
const std::vector<double>& extruder_heights() const { return m_extruder_printable_height; }
|
||||
const BuildSharedVolume& get_shared_volume() const { return m_shared_volume; }
|
||||
|
||||
// Derived data
|
||||
BuildVolume_Type type() const { return m_type; }
|
||||
// Format the type for console output.
|
||||
static std::string_view type_name(BuildVolume_Type type);
|
||||
std::string_view type_name() const { return type_name(m_type); }
|
||||
bool valid() const { return m_type != BuildVolume_Type::Invalid; }
|
||||
// Same as printable_area(), but scaled coordinates.
|
||||
const Polygon& polygon() const { return m_polygon; }
|
||||
// Bounding box of polygon(), scaled.
|
||||
const BoundingBox& bounding_box() const { return m_bbox; }
|
||||
// Bounding volume of printable_area(), printable_height(), unscaled.
|
||||
const BoundingBoxf3& bounding_volume() const { return m_bboxf; }
|
||||
BoundingBoxf bounding_volume2d() const { return { to_2d(m_bboxf.min), to_2d(m_bboxf.max) }; }
|
||||
indexed_triangle_set bounding_mesh(bool scale=true) const;
|
||||
|
||||
// Center of the print bed, unscaled.
|
||||
Vec2d bed_center() const { return to_2d(m_bboxf.center()); }
|
||||
// Convex hull of polygon(), scaled.
|
||||
const Polygon& convex_hull() const { return m_convex_hull; }
|
||||
// Smallest enclosing circle of polygon(), scaled.
|
||||
const Geometry::Circled& circle() const { return m_circle; }
|
||||
|
||||
enum class ObjectState : unsigned char
|
||||
{
|
||||
// Inside the build volume, thus printable.
|
||||
Inside,
|
||||
// Colliding with the build volume boundary, thus not printable and error is shown.
|
||||
Colliding,
|
||||
// Outside of the build volume means the object is ignored: Not printed and no error is shown.
|
||||
Outside,
|
||||
// Completely below the print bed. The same as Outside, but an object with one printable part below the print bed
|
||||
// and at least one part above the print bed is still printable.
|
||||
Below,
|
||||
//in Limited area
|
||||
Limited
|
||||
};
|
||||
|
||||
// 1) Tests called on the plater.
|
||||
// Using SceneEpsilon for all tests.
|
||||
static constexpr const double SceneEpsilon = EPSILON;
|
||||
// Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing.
|
||||
// Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state()
|
||||
// Using SceneEpsilon
|
||||
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, bool ignore_bottom = true) const;
|
||||
// Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example.
|
||||
// Called for a rectangular bed:
|
||||
ObjectState volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom = true) const;
|
||||
|
||||
// 2) Test called on G-code paths.
|
||||
// Using BedEpsilon for all tests.
|
||||
static constexpr const double BedEpsilon = 3. * EPSILON;
|
||||
// Called on final G-code paths.
|
||||
//FIXME The test does not take the thickness of the extrudates into account!
|
||||
bool all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom = true) const;
|
||||
|
||||
int get_extruder_area_count() const { return m_extruder_volumes.size(); }
|
||||
const BuildExtruderVolume& get_extruder_area_volume(int index) const;
|
||||
ObjectState check_object_state_with_extruder_area(const indexed_triangle_set &its, const Transform3f &trafo, int index) const;
|
||||
ObjectState check_object_state_with_extruder_areas(const indexed_triangle_set &its, const Transform3f &trafo, std::vector<bool>& inside_extruders) const;
|
||||
ObjectState check_volume_bbox_state_with_extruder_area(const BoundingBoxf3& volume_bbox, int index) const;
|
||||
ObjectState check_volume_bbox_state_with_extruder_areas(const BoundingBoxf3& volume_bbox, std::vector<bool>& inside_extruders) const;
|
||||
|
||||
const std::pair<std::vector<Vec2d>, std::vector<Vec2d>>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; }
|
||||
const std::pair<std::vector<Vec2d>, std::vector<Vec2d>>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; }
|
||||
|
||||
private:
|
||||
// Source definition of the print bed geometry (PrintConfig::printable_area)
|
||||
std::vector<Vec2d> m_bed_shape;
|
||||
//BBS: extruder shapes
|
||||
std::vector<std::vector<Vec2d>> m_extruder_shapes; //original data from config
|
||||
std::vector<BuildExtruderVolume> m_extruder_volumes;
|
||||
BuildSharedVolume m_shared_volume; //used for rendering
|
||||
// Source definition of the print volume height (PrintConfig::printable_height)
|
||||
double m_max_print_height { 0.f };
|
||||
std::vector<double> m_extruder_printable_height;
|
||||
|
||||
// Derived values.
|
||||
BuildVolume_Type m_type { BuildVolume_Type::Invalid };
|
||||
// Geometry of the print bed, scaled copy of m_bed_shape.
|
||||
Polygon m_polygon;
|
||||
// Scaled snug bounding box around m_polygon.
|
||||
BoundingBox m_bbox;
|
||||
// 3D bounding box around m_shape, m_max_print_height.
|
||||
BoundingBoxf3 m_bboxf;
|
||||
// Area of m_polygon, scaled.
|
||||
double m_area { 0. };
|
||||
// Convex hull of m_polygon, scaled.
|
||||
Polygon m_convex_hull;
|
||||
// For collision detection against a convex build volume. Only filled in for m_type == Convex or Custom.
|
||||
// Variant with SceneEpsilon applied.
|
||||
std::pair<std::vector<Vec2d>, std::vector<Vec2d>> m_top_bottom_convex_hull_decomposition_scene;
|
||||
// Variant with BedEpsilon applied.
|
||||
std::pair<std::vector<Vec2d>, std::vector<Vec2d>> m_top_bottom_convex_hull_decomposition_bed;
|
||||
// Smallest enclosing circle of m_polygon, scaled.
|
||||
Geometry::Circled m_circle { Vec2d::Zero(), 0 };
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_BuildVolume_hpp_
|
||||
@@ -0,0 +1,636 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(libslic3r)
|
||||
|
||||
include(PrecompiledHeader)
|
||||
|
||||
if(NOT DEFINED ORCA_CHECK_GCODE_PLACEHOLDERS)
|
||||
set(ORCA_CHECK_GCODE_PLACEHOLDERS "0")
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY)
|
||||
|
||||
if (MINGW)
|
||||
add_compile_options(-Wa,-mbig-obj)
|
||||
endif ()
|
||||
|
||||
set(OpenVDBUtils_SOURCES "")
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
|
||||
endif()
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
|
||||
|
||||
set(lisbslic3r_sources
|
||||
AABBMesh.cpp
|
||||
AABBMesh.hpp
|
||||
AABBTreeIndirect.hpp
|
||||
AABBTreeLines.hpp
|
||||
Algorithm/LineSplit.cpp
|
||||
Algorithm/LineSplit.hpp
|
||||
Algorithm/RegionExpansion.cpp
|
||||
Algorithm/RegionExpansion.hpp
|
||||
AnyPtr.hpp
|
||||
AppConfig.cpp
|
||||
AppConfig.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.cpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.hpp
|
||||
Arachne/SkeletalTrapezoidation.cpp
|
||||
Arachne/SkeletalTrapezoidationEdge.hpp
|
||||
Arachne/SkeletalTrapezoidationGraph.cpp
|
||||
Arachne/SkeletalTrapezoidationGraph.hpp
|
||||
Arachne/SkeletalTrapezoidation.hpp
|
||||
Arachne/SkeletalTrapezoidationJoint.hpp
|
||||
Arachne/utils/ExtrusionJunction.hpp
|
||||
Arachne/utils/ExtrusionLine.cpp
|
||||
Arachne/utils/ExtrusionLine.hpp
|
||||
Arachne/utils/HalfEdgeGraph.hpp
|
||||
Arachne/utils/HalfEdge.hpp
|
||||
Arachne/utils/HalfEdgeNode.hpp
|
||||
Arachne/utils/PolygonsPointIndex.hpp
|
||||
Arachne/utils/PolygonsSegmentIndex.hpp
|
||||
Arachne/utils/PolylineStitcher.cpp
|
||||
Arachne/utils/PolylineStitcher.hpp
|
||||
Arachne/utils/SparseGrid.hpp
|
||||
Arachne/utils/SparseLineGrid.hpp
|
||||
Arachne/utils/SparsePointGrid.hpp
|
||||
Arachne/utils/SquareGrid.cpp
|
||||
Arachne/utils/SquareGrid.hpp
|
||||
Arachne/WallToolPaths.cpp
|
||||
Arachne/WallToolPaths.hpp
|
||||
ArcFitter.cpp
|
||||
ArcFitter.hpp
|
||||
Arrange.cpp
|
||||
Arrange.hpp
|
||||
BlacklistedLibraryCheck.cpp
|
||||
BlacklistedLibraryCheck.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
BridgeDetector.cpp
|
||||
BridgeDetector.hpp
|
||||
Brim.cpp
|
||||
BrimEarsPoint.hpp
|
||||
Brim.hpp
|
||||
BuildVolume.cpp
|
||||
BuildVolume.hpp
|
||||
calib.cpp
|
||||
calib.hpp
|
||||
Circle.cpp
|
||||
Circle.hpp
|
||||
clipper.cpp
|
||||
clipper.hpp
|
||||
ClipperUtils.cpp
|
||||
ClipperUtils.hpp
|
||||
Clipper2Utils.cpp
|
||||
Clipper2Utils.hpp
|
||||
ClipperZUtils.hpp
|
||||
Color.cpp
|
||||
Color.hpp
|
||||
CommonDefs.hpp
|
||||
Config.cpp
|
||||
Config.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
CutUtils.cpp
|
||||
CutUtils.hpp
|
||||
EdgeGrid.cpp
|
||||
EdgeGrid.hpp
|
||||
ElephantFootCompensation.cpp
|
||||
ElephantFootCompensation.hpp
|
||||
Emboss.cpp
|
||||
Emboss.hpp
|
||||
EmbossShape.hpp
|
||||
enum_bitmask.hpp
|
||||
Execution/Execution.hpp
|
||||
Execution/ExecutionSeq.hpp
|
||||
Execution/ExecutionTBB.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonSerialize.hpp
|
||||
ExPolygonsIndex.cpp
|
||||
ExPolygonsIndex.hpp
|
||||
Extruder.cpp
|
||||
Extruder.hpp
|
||||
ExtrusionEntityCollection.cpp
|
||||
ExtrusionEntityCollection.hpp
|
||||
ExtrusionEntity.cpp
|
||||
ExtrusionEntity.hpp
|
||||
ExtrusionSimulator.cpp
|
||||
ExtrusionSimulator.hpp
|
||||
FaceDetector.cpp
|
||||
FaceDetector.hpp
|
||||
Feature/FuzzySkin/FuzzySkin.cpp
|
||||
Feature/FuzzySkin/FuzzySkin.hpp
|
||||
Feature/Interlocking/InterlockingGenerator.cpp
|
||||
Feature/Interlocking/InterlockingGenerator.hpp
|
||||
Feature/Interlocking/VoxelUtils.cpp
|
||||
Feature/Interlocking/VoxelUtils.hpp
|
||||
FileParserError.hpp
|
||||
Fill/Fill3DHoneycomb.cpp
|
||||
Fill/Fill3DHoneycomb.hpp
|
||||
Fill/FillAdaptive.cpp
|
||||
Fill/FillAdaptive.hpp
|
||||
Fill/FillBase.cpp
|
||||
Fill/FillBase.hpp
|
||||
Fill/FillConcentric.cpp
|
||||
Fill/FillConcentric.hpp
|
||||
Fill/FillConcentricInternal.cpp
|
||||
Fill/FillConcentricInternal.hpp
|
||||
Fill/Fill.cpp
|
||||
Fill/FillCrossHatch.cpp
|
||||
Fill/FillCrossHatch.hpp
|
||||
Fill/FillGyroid.cpp
|
||||
Fill/FillGyroid.hpp
|
||||
Fill/FillHoneycomb.cpp
|
||||
Fill/FillHoneycomb.hpp
|
||||
Fill/Fill.hpp
|
||||
Fill/FillLightning.cpp
|
||||
Fill/FillLightning.hpp
|
||||
Fill/FillLine.cpp
|
||||
Fill/FillLine.hpp
|
||||
Fill/FillPlanePath.cpp
|
||||
Fill/FillPlanePath.hpp
|
||||
Fill/FillRectilinear.cpp
|
||||
Fill/FillRectilinear.hpp
|
||||
Fill/FillTpmsD.cpp
|
||||
Fill/FillTpmsD.hpp
|
||||
Fill/FillTpmsFK.cpp
|
||||
Fill/FillTpmsFK.hpp
|
||||
Fill/Lightning/DistanceField.cpp
|
||||
Fill/Lightning/DistanceField.hpp
|
||||
Fill/Lightning/Generator.cpp
|
||||
Fill/Lightning/Generator.hpp
|
||||
Fill/Lightning/Layer.cpp
|
||||
Fill/Lightning/Layer.hpp
|
||||
Fill/Lightning/TreeNode.cpp
|
||||
Fill/Lightning/TreeNode.hpp
|
||||
Flow.cpp
|
||||
Flow.hpp
|
||||
FlushVolCalc.cpp
|
||||
FlushVolCalc.hpp
|
||||
Format/3mf.cpp
|
||||
Format/3mf.hpp
|
||||
Format/AMF.cpp
|
||||
Format/AMF.hpp
|
||||
Format/DRC.cpp
|
||||
Format/DRC.hpp
|
||||
Format/bbs_3mf.cpp
|
||||
Format/bbs_3mf.hpp
|
||||
format.hpp
|
||||
Format/OBJ.cpp
|
||||
Format/OBJ.hpp
|
||||
Format/objparser.cpp
|
||||
Format/objparser.hpp
|
||||
Format/SL1.cpp
|
||||
Format/SL1.hpp
|
||||
Format/STEP.cpp
|
||||
Format/STEP.hpp
|
||||
Format/STL.cpp
|
||||
Format/STL.hpp
|
||||
Format/svg.cpp
|
||||
Format/svg.hpp
|
||||
Format/ZipperArchiveImport.cpp
|
||||
Format/ZipperArchiveImport.hpp
|
||||
GCode/AdaptivePAInterpolator.cpp
|
||||
GCode/AdaptivePAInterpolator.hpp
|
||||
GCode/AdaptivePAProcessor.cpp
|
||||
GCode/AdaptivePAProcessor.hpp
|
||||
GCode/AvoidCrossingPerimeters.cpp
|
||||
GCode/AvoidCrossingPerimeters.hpp
|
||||
GCode/ConflictChecker.cpp
|
||||
GCode/ConflictChecker.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/TimelapsePosPicker.cpp
|
||||
GCode/TimelapsePosPicker.hpp
|
||||
GCode.cpp
|
||||
GCode/ExtrusionProcessor.hpp
|
||||
GCode/FanMover.cpp
|
||||
GCode/FanMover.hpp
|
||||
GCode/GCodeProcessor.cpp
|
||||
GCode/GCodeProcessor.hpp
|
||||
GCode.hpp
|
||||
GCode/PchipInterpolatorHelper.cpp
|
||||
GCode/PchipInterpolatorHelper.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
GCode/PressureEqualizer.cpp
|
||||
GCode/PressureEqualizer.hpp
|
||||
GCode/PrintExtents.cpp
|
||||
GCode/PrintExtents.hpp
|
||||
GCodeReader.cpp
|
||||
GCodeReader.hpp
|
||||
GCode/RetractWhenCrossingPerimeters.cpp
|
||||
GCode/RetractWhenCrossingPerimeters.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
GCode/SeamPlacer.hpp
|
||||
#GCodeSender.cpp
|
||||
#GCodeSender.hpp
|
||||
GCode/SmallAreaInfillFlowCompensator.cpp
|
||||
GCode/SmallAreaInfillFlowCompensator.hpp
|
||||
GCode/SpiralVase.cpp
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/ThumbnailData.cpp
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/Thumbnails.cpp
|
||||
GCode/Thumbnails.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/WipeTower2.cpp
|
||||
GCode/WipeTower2.hpp
|
||||
GCode/WipeTower.cpp
|
||||
GCode/WipeTower.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry/ArcWelder.hpp
|
||||
Geometry/ArcWelder.cpp
|
||||
Geometry/Bicubic.hpp
|
||||
Geometry/Circle.cpp
|
||||
Geometry/Circle.hpp
|
||||
Geometry/ConvexHull.cpp
|
||||
Geometry/ConvexHull.hpp
|
||||
Geometry.cpp
|
||||
Geometry/Curves.hpp
|
||||
Geometry.hpp
|
||||
Geometry/MedialAxis.cpp
|
||||
Geometry/MedialAxis.hpp
|
||||
Geometry/Voronoi.cpp
|
||||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiUtilsCgal.cpp
|
||||
Geometry/VoronoiUtilsCgal.hpp
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
Layer.hpp
|
||||
LayerRegion.cpp
|
||||
libslic3r.cpp
|
||||
libslic3r.h
|
||||
Line.cpp
|
||||
Line.hpp
|
||||
LocalesUtils.cpp
|
||||
LocalesUtils.hpp
|
||||
MarchingSquares.hpp
|
||||
Measure.cpp
|
||||
Measure.hpp
|
||||
MeasureUtils.hpp
|
||||
MeshSplitImpl.hpp
|
||||
MinAreaBoundingBox.cpp
|
||||
MinAreaBoundingBox.hpp
|
||||
MinimumSpanningTree.cpp
|
||||
MinimumSpanningTree.hpp
|
||||
miniz_extension.cpp
|
||||
miniz_extension.hpp
|
||||
ModelArrange.cpp
|
||||
ModelArrange.hpp
|
||||
Model.cpp
|
||||
Model.hpp
|
||||
MTUtils.hpp
|
||||
MultiMaterialSegmentation.cpp
|
||||
MultiMaterialSegmentation.hpp
|
||||
MultiPoint.cpp
|
||||
MultiPoint.hpp
|
||||
MutablePolygon.cpp
|
||||
MutablePolygon.hpp
|
||||
MutablePriorityQueue.hpp
|
||||
NormalUtils.cpp
|
||||
NormalUtils.hpp
|
||||
NSVGUtils.cpp
|
||||
NSVGUtils.hpp
|
||||
ObjColorUtils.cpp
|
||||
ObjColorUtils.hpp
|
||||
ObjectID.cpp
|
||||
ObjectID.hpp
|
||||
Optimize/BruteforceOptimizer.hpp
|
||||
Optimize/NLoptOptimizer.hpp
|
||||
Optimize/Optimizer.hpp
|
||||
Orient.cpp
|
||||
Orient.hpp
|
||||
ParameterUtils.cpp
|
||||
ParameterUtils.hpp
|
||||
pchheader.cpp
|
||||
pchheader.hpp
|
||||
PerimeterGenerator.cpp
|
||||
PerimeterGenerator.hpp
|
||||
PlaceholderParser.cpp
|
||||
PlaceholderParser.hpp
|
||||
Platform.cpp
|
||||
Platform.hpp
|
||||
PNGReadWrite.cpp
|
||||
PNGReadWrite.hpp
|
||||
Point.cpp
|
||||
Point.hpp
|
||||
Polygon.cpp
|
||||
Polygon.hpp
|
||||
PolygonTrimmer.cpp
|
||||
PolygonTrimmer.hpp
|
||||
Polyline.cpp
|
||||
Polyline.hpp
|
||||
PresetBundle.cpp
|
||||
PresetBundle.hpp
|
||||
Preset.cpp
|
||||
Preset.hpp
|
||||
PrincipalComponents2D.cpp
|
||||
PrincipalComponents2D.hpp
|
||||
PrintApply.cpp
|
||||
PrintBase.cpp
|
||||
PrintBase.hpp
|
||||
MaterialType.cpp
|
||||
MaterialType.hpp
|
||||
PrintConfig.cpp
|
||||
PrintConfig.hpp
|
||||
Print.cpp
|
||||
Print.hpp
|
||||
PrintObject.cpp
|
||||
PrintObjectSlice.cpp
|
||||
PrintRegion.cpp
|
||||
ProjectTask.cpp
|
||||
ProjectTask.hpp
|
||||
QuadricEdgeCollapse.cpp
|
||||
QuadricEdgeCollapse.hpp
|
||||
Semver.cpp
|
||||
Shape/TextShape.cpp
|
||||
Shape/TextShape.hpp
|
||||
ShortEdgeCollapse.cpp
|
||||
ShortEdgeCollapse.hpp
|
||||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
SLA/AGGRaster.hpp
|
||||
SLA/BoostAdapter.hpp
|
||||
SLA/Clustering.cpp
|
||||
SLA/Clustering.hpp
|
||||
SLA/ConcaveHull.cpp
|
||||
SLA/ConcaveHull.hpp
|
||||
SLA/Concurrency.hpp
|
||||
SLA/Hollowing.cpp
|
||||
SLA/Hollowing.hpp
|
||||
SLA/IndexedMesh.cpp
|
||||
SLA/IndexedMesh.hpp
|
||||
SLA/JobController.hpp
|
||||
SLA/Pad.cpp
|
||||
SLA/Pad.hpp
|
||||
SLAPrint.cpp
|
||||
SLAPrint.hpp
|
||||
SLAPrintSteps.cpp
|
||||
SLAPrintSteps.hpp
|
||||
SLA/RasterBase.cpp
|
||||
SLA/RasterBase.hpp
|
||||
SLA/RasterToPolygons.cpp
|
||||
SLA/RasterToPolygons.hpp
|
||||
SLA/ReprojectPointsOnMesh.hpp
|
||||
SLA/Rotfinder.cpp
|
||||
SLA/Rotfinder.hpp
|
||||
SLA/SpatIndex.cpp
|
||||
SLA/SpatIndex.hpp
|
||||
SLA/SupportPointGenerator.cpp
|
||||
SLA/SupportPointGenerator.hpp
|
||||
SLA/SupportPoint.hpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
SLA/SupportTreeBuilder.hpp
|
||||
SLA/SupportTreeBuildsteps.cpp
|
||||
SLA/SupportTreeBuildsteps.hpp
|
||||
SLA/SupportTree.cpp
|
||||
SLA/SupportTree.hpp
|
||||
#SLA/SupportTreeIGL.cpp
|
||||
SLA/SupportTreeMesher.cpp
|
||||
SLA/SupportTreeMesher.hpp
|
||||
SlicesToTriangleMesh.cpp
|
||||
SlicesToTriangleMesh.hpp
|
||||
SlicingAdaptive.cpp
|
||||
SlicingAdaptive.hpp
|
||||
Slicing.cpp
|
||||
Slicing.hpp
|
||||
Support/SupportCommon.cpp
|
||||
Support/SupportCommon.hpp
|
||||
Support/SupportLayer.hpp
|
||||
Support/SupportMaterial.cpp
|
||||
Support/SupportMaterial.hpp
|
||||
Support/SupportParameters.hpp
|
||||
Support/SupportSpotsGenerator.cpp
|
||||
Support/SupportSpotsGenerator.hpp
|
||||
Support/TreeModelVolumes.cpp
|
||||
Support/TreeModelVolumes.hpp
|
||||
Support/TreeSupport3D.cpp
|
||||
Support/TreeSupport3D.hpp
|
||||
Support/TreeSupportCommon.hpp
|
||||
Support/TreeSupport.cpp
|
||||
Support/TreeSupport.hpp
|
||||
SurfaceCollection.cpp
|
||||
SurfaceCollection.hpp
|
||||
Surface.cpp
|
||||
Surface.hpp
|
||||
SurfaceMesh.hpp
|
||||
SVG.cpp
|
||||
SVG.hpp
|
||||
Technologies.hpp
|
||||
Tesselate.cpp
|
||||
Tesselate.hpp
|
||||
TextConfiguration.hpp
|
||||
Thread.cpp
|
||||
Thread.hpp
|
||||
Time.cpp
|
||||
Time.hpp
|
||||
Timer.cpp
|
||||
Timer.hpp
|
||||
TriangleMesh.cpp
|
||||
TriangleMesh.hpp
|
||||
TriangleMeshDeal.cpp
|
||||
TriangleMeshDeal.hpp
|
||||
TriangleMeshSlicer.cpp
|
||||
TriangleMeshSlicer.hpp
|
||||
TriangleSelector.cpp
|
||||
TriangleSelector.hpp
|
||||
TriangleSetSampling.cpp
|
||||
TriangleSetSampling.hpp
|
||||
TriangulateWall.cpp
|
||||
TriangulateWall.hpp
|
||||
utils.cpp
|
||||
Utils.hpp
|
||||
VariableWidth.cpp
|
||||
VariableWidth.hpp
|
||||
Zipper.cpp
|
||||
Zipper.hpp
|
||||
FilamentGroup.hpp
|
||||
FilamentGroup.cpp
|
||||
FilamentGroupUtils.hpp
|
||||
FilamentGroupUtils.cpp
|
||||
GCode/ToolOrderUtils.hpp
|
||||
GCode/ToolOrderUtils.cpp
|
||||
FlushVolPredictor.hpp
|
||||
FlushVolPredictor.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND lisbslic3r_sources
|
||||
MacUtils.mm
|
||||
Format/ModelIO.hpp
|
||||
Format/ModelIO.mm
|
||||
)
|
||||
endif ()
|
||||
|
||||
add_library(libslic3r STATIC ${lisbslic3r_sources}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
||||
${OpenVDBUtils_SOURCES})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${lisbslic3r_sources})
|
||||
|
||||
if (SLIC3R_STATIC)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL REQUIRED)
|
||||
find_package(OpenCV REQUIRED core)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC
|
||||
CutSurface.hpp CutSurface.cpp
|
||||
IntersectionPoints.hpp IntersectionPoints.cpp
|
||||
MeshBoolean.hpp MeshBoolean.cpp
|
||||
TryCatchSignal.hpp TryCatchSignal.cpp
|
||||
Triangulation.hpp Triangulation.cpp
|
||||
)
|
||||
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options
|
||||
# (-frounding-math) still propagate to dependent libs which is not desired.
|
||||
get_target_property(_cgal_tgt CGAL::CGAL ALIASED_TARGET)
|
||||
if (NOT TARGET ${_cgal_tgt})
|
||||
set (_cgal_tgt CGAL::CGAL)
|
||||
endif ()
|
||||
get_target_property(_opts ${_cgal_tgt} INTERFACE_COMPILE_OPTIONS)
|
||||
if (_opts)
|
||||
set(_opts_bad "${_opts}")
|
||||
set(_opts_good "${_opts}")
|
||||
list(FILTER _opts_bad INCLUDE REGEX frounding-math)
|
||||
list(FILTER _opts_good EXCLUDE REGEX frounding-math)
|
||||
set_target_properties(${_cgal_tgt} PROPERTIES INTERFACE_COMPILE_OPTIONS "${_opts_good}")
|
||||
target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}")
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} eigen admesh libigl mcut boost_libs)
|
||||
|
||||
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
|
||||
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF)
|
||||
endif ()
|
||||
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
# Find the OCCT and related libraries
|
||||
set(OpenCASCADE_DIR "${CMAKE_PREFIX_PATH}/lib/cmake/occt")
|
||||
find_package(OpenCASCADE REQUIRED)
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${OpenCASCADE_INCLUDE_DIR})
|
||||
|
||||
find_package(JPEG REQUIRED)
|
||||
find_package(draco REQUIRED)
|
||||
|
||||
set(OCCT_LIBS
|
||||
TKXDESTEP
|
||||
TKSTEP
|
||||
TKSTEP209
|
||||
TKSTEPAttr
|
||||
TKSTEPBase
|
||||
TKXCAF
|
||||
TKXSBase
|
||||
TKVCAF
|
||||
TKCAF
|
||||
TKLCAF
|
||||
TKCDF
|
||||
TKV3d
|
||||
TKService
|
||||
TKMesh
|
||||
TKBO
|
||||
TKPrim
|
||||
TKHLR
|
||||
TKShHealing
|
||||
TKTopAlgo
|
||||
TKGeomAlgo
|
||||
TKBRep
|
||||
TKGeomBase
|
||||
TKG3d
|
||||
TKG2d
|
||||
TKMath
|
||||
TKernel
|
||||
)
|
||||
|
||||
target_link_libraries(libslic3r
|
||||
PUBLIC
|
||||
admesh
|
||||
libigl
|
||||
libnest2d
|
||||
miniz
|
||||
opencv_world
|
||||
PRIVATE
|
||||
${CMAKE_DL_LIBS}
|
||||
${EXPAT_LIBRARIES}
|
||||
${OCCT_LIBS}
|
||||
boost_libs
|
||||
cereal::cereal
|
||||
clipper
|
||||
Clipper2
|
||||
draco::draco
|
||||
eigen
|
||||
glu-libtess
|
||||
JPEG::JPEG
|
||||
libslic3r_cgal
|
||||
mcut
|
||||
noise::noise
|
||||
PNG::PNG
|
||||
qhull
|
||||
qoi
|
||||
semver
|
||||
TBB::tbb
|
||||
TBB::tbbmalloc
|
||||
ZLIB::ZLIB
|
||||
OpenSSL::Crypto
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
# Link freetype for OCCT dependency (CAD operations need font rendering)
|
||||
target_link_libraries(libslic3r PRIVATE ${FREETYPE_LIBRARIES})
|
||||
if (NOT APPLE)
|
||||
target_link_libraries(libslic3r PRIVATE fontconfig)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
find_library(FOUNDATION Foundation REQUIRED)
|
||||
find_library(MODELIO ModelIO REQUIRED)
|
||||
target_link_libraries(libslic3r PRIVATE ${FOUNDATION} ${MODELIO})
|
||||
endif ()
|
||||
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
target_link_libraries(libslic3r PRIVATE OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(libslic3r PRIVATE Psapi.lib bcrypt.lib)
|
||||
endif()
|
||||
|
||||
if(SLIC3R_PROFILE)
|
||||
target_link_libraries(libslic3r PRIVATE Shiny)
|
||||
endif()
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
@@ -0,0 +1,121 @@
|
||||
#ifndef CSGMESH_HPP
|
||||
#define CSGMESH_HPP
|
||||
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <admesh/stl.h>
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// A CSGPartT should be an object that can provide at least a mesh + trafo and an
|
||||
// associated csg operation. A collection of CSGPartT objects can then
|
||||
// be interpreted as one model and used in various contexts. It can be assembled
|
||||
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to
|
||||
// deal with various parts of it according to the supported CSG types...
|
||||
//
|
||||
// A few simple templated interface functions are provided here and a default
|
||||
// CSGPart class that implements the necessary means to be usable as a
|
||||
// CSGPartT object.
|
||||
|
||||
// Supported CSG operation types
|
||||
enum class CSGType { Union, Difference, Intersection };
|
||||
|
||||
// A CSG part can instruct the processing to push the sub-result until a new
|
||||
// csg part with a pop instruction appears. This can be used to implement
|
||||
// parentheses in a CSG expression represented by the collection of csg parts.
|
||||
// A CSG part can not contain another CSG collection, only a mesh, this is why
|
||||
// its easier to do this stacking instead of recursion in the data definition.
|
||||
// CSGStackOp::Continue means no stack operation required.
|
||||
// When a CSG part contains a Push instruction, it is expected that the CSG
|
||||
// operation it contains refers to the whole collection spanning to the nearest
|
||||
// part with a Pop instruction.
|
||||
// e.g.:
|
||||
// {
|
||||
// CUBE1: { mesh: cube, op: Union, stack op: Continue },
|
||||
// CUBE2: { mesh: cube, op: Difference, stack op: Push},
|
||||
// CUBE3: { mesh: cube, op: Union, stack op: Pop}
|
||||
// }
|
||||
// is a collection of csg parts representing the expression CUBE1 - (CUBE2 + CUBE3)
|
||||
enum class CSGStackOp { Push, Continue, Pop };
|
||||
|
||||
// Get the CSG operation of the part. Can be overriden for any type
|
||||
template<class CSGPartT> CSGType get_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.operation;
|
||||
}
|
||||
|
||||
// Get the stack operation required by the CSG part.
|
||||
template<class CSGPartT> CSGStackOp get_stack_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.stack_operation;
|
||||
}
|
||||
|
||||
// Get the mesh for the part. Can be overriden for any type
|
||||
template<class CSGPartT>
|
||||
const indexed_triangle_set *get_mesh(const CSGPartT &part)
|
||||
{
|
||||
return part.its_ptr.get();
|
||||
}
|
||||
|
||||
// Get the transformation associated with the mesh inside a CSGPartT object.
|
||||
// Can be overriden for any type.
|
||||
template<class CSGPartT>
|
||||
Transform3f get_transform(const CSGPartT &part)
|
||||
{
|
||||
return part.trafo;
|
||||
}
|
||||
|
||||
// Default implementation
|
||||
struct CSGPart {
|
||||
AnyPtr<const indexed_triangle_set> its_ptr;
|
||||
Transform3f trafo;
|
||||
CSGType operation;
|
||||
CSGStackOp stack_operation;
|
||||
std::string name;
|
||||
|
||||
CSGPart(AnyPtr<const indexed_triangle_set> ptr = {},
|
||||
CSGType op = CSGType::Union,
|
||||
const Transform3f &tr = Transform3f::Identity())
|
||||
: its_ptr{std::move(ptr)}
|
||||
, operation{op}
|
||||
, stack_operation{CSGStackOp::Continue}
|
||||
, trafo{tr}
|
||||
{}
|
||||
};
|
||||
|
||||
//Prusa
|
||||
// Check if there are only positive parts (Union) within the collection.
|
||||
template<class Cont> bool is_all_positive(const Cont &csgmesh)
|
||||
{
|
||||
bool is_all_pos =
|
||||
std::all_of(csgmesh.begin(),
|
||||
csgmesh.end(),
|
||||
[](auto &part) {
|
||||
return csg::get_operation(part) == csg::CSGType::Union;
|
||||
});
|
||||
|
||||
return is_all_pos;
|
||||
}
|
||||
|
||||
//Prusa
|
||||
// Merge all the positive parts of the collection into a single triangle mesh without performing
|
||||
// any booleans.
|
||||
template<class Cont>
|
||||
indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh)
|
||||
{
|
||||
indexed_triangle_set m;
|
||||
for (auto &csgpart : csgmesh) {
|
||||
auto op = csg::get_operation(csgpart);
|
||||
const indexed_triangle_set * pmesh = csg::get_mesh(csgpart);
|
||||
if (pmesh && op == csg::CSGType::Union) {
|
||||
indexed_triangle_set mcpy = *pmesh;
|
||||
its_transform(mcpy, csg::get_transform(csgpart), true);
|
||||
its_merge(m, mcpy);
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGMESH_HPP
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef CSGMESHCOPY_HPP
|
||||
#define CSGMESHCOPY_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Copy a csg range but for the meshes, only copy the pointers. If the copy
|
||||
// is made from a CSGPart compatible object, and the pointer is a shared one,
|
||||
// it will be copied with reference counting.
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_shallow(const Range<It> &csgrange, OutIt out)
|
||||
{
|
||||
for (const auto &part : csgrange) {
|
||||
CSGPart cpy{{},
|
||||
get_operation(part),
|
||||
get_transform(part)};
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
if constexpr (std::is_convertible_v<decltype(part), const CSGPart&>) {
|
||||
if (auto shptr = part.its_ptr.get_shared_cpy()) {
|
||||
cpy.its_ptr = shptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cpy.its_ptr)
|
||||
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the csg range, allocating new meshes
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_deep(const Range<It> &csgrange, OutIt out)
|
||||
{
|
||||
for (const auto &part : csgrange) {
|
||||
|
||||
CSGPart cpy{{}, get_operation(part), get_transform(part)};
|
||||
|
||||
if (auto meshptr = get_mesh(part)) {
|
||||
cpy.its_ptr = std::make_unique<const indexed_triangle_set>(*meshptr);
|
||||
}
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
template<class ItA, class ItB>
|
||||
bool is_same(const Range<ItA> &A, const Range<ItB> &B)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
size_t s = A.size();
|
||||
|
||||
if (B.size() != s)
|
||||
ret = false;
|
||||
|
||||
size_t i = 0;
|
||||
auto itA = A.begin();
|
||||
auto itB = B.begin();
|
||||
for (; ret && i < s; ++itA, ++itB, ++i) {
|
||||
ret = ret &&
|
||||
get_mesh(*itA) == get_mesh(*itB) &&
|
||||
get_operation(*itA) == get_operation(*itB) &&
|
||||
get_stack_operation(*itA) == get_stack_operation(*itB) &&
|
||||
get_transform(*itA).isApprox(get_transform(*itB));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGCOPY_HPP
|
||||
@@ -0,0 +1,92 @@
|
||||
#ifndef MODELTOCSGMESH_HPP
|
||||
#define MODELTOCSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include "libslic3r/MeshSplitImpl.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Flags to select which parts to export from Model into a csg part collection.
|
||||
// These flags can be chained with the | operator
|
||||
enum ModelParts {
|
||||
mpartsPositive = 1, // Include positive parts
|
||||
mpartsNegative = 2, // Include negative parts
|
||||
mpartsDrillHoles = 4, // Include drill holes
|
||||
mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts
|
||||
};
|
||||
|
||||
template<class OutIt>
|
||||
bool model_to_csgmesh(const ModelObject &mo,
|
||||
const Transform3d &trafo, // Applies to all exported parts
|
||||
OutIt out, // Output iterator
|
||||
// values of ModelParts OR-ed
|
||||
int parts_to_include = mpartsPositive
|
||||
)
|
||||
{
|
||||
bool do_positives = parts_to_include & mpartsPositive;
|
||||
bool do_negatives = parts_to_include & mpartsNegative;
|
||||
bool do_drillholes = parts_to_include & mpartsDrillHoles;
|
||||
bool do_splits = parts_to_include & mpartsDoSplits;
|
||||
bool has_splitable_volume = false;
|
||||
|
||||
for (const ModelVolume *vol : mo.volumes) {
|
||||
if (vol && vol->mesh_ptr() &&
|
||||
((do_positives && vol->is_model_part()) ||
|
||||
(do_negatives && vol->is_negative_volume()))) {
|
||||
|
||||
if (do_splits && its_is_splittable(vol->mesh().its)) {
|
||||
CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference};
|
||||
part_begin.stack_operation = CSGStackOp::Push;
|
||||
*out = std::move(part_begin);
|
||||
++out;
|
||||
|
||||
its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) {
|
||||
if (its.empty())
|
||||
return;
|
||||
|
||||
CSGPart part{std::make_unique<indexed_triangle_set>(std::move(its)),
|
||||
CSGType::Union,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}});
|
||||
|
||||
CSGPart part_end{{}};
|
||||
part_end.stack_operation = CSGStackOp::Pop;
|
||||
*out = std::move(part_end);
|
||||
++out;
|
||||
has_splitable_volume = true;
|
||||
} else {
|
||||
CSGPart part{&(vol->mesh().its),
|
||||
vol->is_model_part() ? CSGType::Union : CSGType::Difference,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
part.name = vol->name;
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (do_drillholes) {
|
||||
// sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo);
|
||||
|
||||
// for (const sla::DrainHole &dhole : drainholes) {
|
||||
// CSGPart part{std::make_unique<const indexed_triangle_set>(
|
||||
// dhole.to_mesh()),
|
||||
// CSGType::Difference};
|
||||
|
||||
// *out = std::move(part);
|
||||
// ++out;
|
||||
// }
|
||||
//}
|
||||
|
||||
return has_splitable_volume;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // MODELTOCSGMESH_HPP
|
||||
@@ -0,0 +1,382 @@
|
||||
#ifndef PERFORMCSGMESHBOOLEANS_HPP
|
||||
#define PERFORMCSGMESHBOOLEANS_HPP
|
||||
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
//#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||
#include "libslic3r/MeshBoolean.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
enum class BooleanFailReason { OK, MeshEmpty, NotBoundAVolume, SelfIntersect, NoIntersection};
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
|
||||
{
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
indexed_triangle_set dummy;
|
||||
|
||||
if (!its)
|
||||
its = &dummy;
|
||||
|
||||
MeshBoolean::cgal::CGALMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
|
||||
} catch (...) {
|
||||
// errors are ignored, simply return null
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
MeshBoolean::mcut::McutMeshPtr get_mcutmesh(const CSGPartT& csgpart)
|
||||
{
|
||||
const indexed_triangle_set* its = csg::get_mesh(csgpart);
|
||||
indexed_triangle_set dummy;
|
||||
|
||||
if (!its)
|
||||
its = &dummy;
|
||||
|
||||
MeshBoolean::mcut::McutMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
ret = MeshBoolean::mcut::triangle_mesh_to_mcut(m);
|
||||
}
|
||||
catch (...) {
|
||||
// errors are ignored, simply return null
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail_cgal {
|
||||
|
||||
using MeshBoolean::cgal::CGALMeshPtr;
|
||||
|
||||
inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src)
|
||||
{
|
||||
if (!dst && op == CSGType::Union && src) {
|
||||
dst = std::move(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
MeshBoolean::cgal::plus(*dst, *src);
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
MeshBoolean::cgal::minus(*dst, *src);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
MeshBoolean::cgal::intersect(*dst, *src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Ex, class It>
|
||||
std::vector<CGALMeshPtr> get_cgalptrs(Ex policy, const Range<It> &csgrange)
|
||||
{
|
||||
std::vector<CGALMeshPtr> ret(csgrange.size());
|
||||
execution::for_each(policy, size_t(0), csgrange.size(),
|
||||
[&csgrange, &ret](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto &csgpart = *it;
|
||||
ret[i] = get_cgalmesh(csgpart);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace detail_mcut {
|
||||
|
||||
using MeshBoolean::mcut::McutMeshPtr;
|
||||
|
||||
inline void perform_csg(CSGType op, McutMeshPtr& dst, McutMeshPtr& src)
|
||||
{
|
||||
if (!dst && op == CSGType::Union && src) {
|
||||
dst = std::move(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"UNION");
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"A_NOT_B");
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"INTERSECTION");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Ex, class It>
|
||||
std::vector<McutMeshPtr> get_mcutptrs(Ex policy, const Range<It>& csgrange)
|
||||
{
|
||||
std::vector<McutMeshPtr> ret(csgrange.size());
|
||||
execution::for_each(policy, size_t(0), csgrange.size(),
|
||||
[&csgrange, &ret](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto& csgpart = *it;
|
||||
ret[i] = get_mcutmesh(csgpart);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace mcut_detail
|
||||
|
||||
// Process the sequence of CSG parts with CGAL.
|
||||
template<class It>
|
||||
void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm,
|
||||
const Range<It> &csgrange)
|
||||
{
|
||||
using MeshBoolean::cgal::CGALMesh;
|
||||
using MeshBoolean::cgal::CGALMeshPtr;
|
||||
using namespace detail_cgal;
|
||||
|
||||
struct Frame {
|
||||
CSGType op; CGALMeshPtr cgalptr;
|
||||
explicit Frame(CSGType csgop = CSGType::Union)
|
||||
: op{ csgop }
|
||||
, cgalptr{ MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}) }
|
||||
{}
|
||||
};
|
||||
|
||||
std::stack opstack{ std::vector<Frame>{} };
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<CGALMeshPtr> cgalmeshes = get_cgalptrs(ex_tbb, csgrange);
|
||||
|
||||
size_t csgidx = 0;
|
||||
for (auto& csgpart : csgrange) {
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
CGALMeshPtr& cgalptr = cgalmeshes[csgidx++];
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push(Frame{ op });
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame* top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->cgalptr, cgalptr);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
CGALMeshPtr src = std::move(top->cgalptr);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
CGALMeshPtr& dst = opstack.top().cgalptr;
|
||||
perform_csg(popop, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
cgalm = std::move(opstack.top().cgalptr);
|
||||
}
|
||||
|
||||
// Process the sequence of CSG parts with mcut.
|
||||
template<class It>
|
||||
void perform_csgmesh_booleans_mcut(MeshBoolean::mcut::McutMeshPtr& mcutm,
|
||||
const Range<It>& csgrange)
|
||||
{
|
||||
using MeshBoolean::mcut::McutMesh;
|
||||
using MeshBoolean::mcut::McutMeshPtr;
|
||||
using namespace detail_mcut;
|
||||
|
||||
struct Frame {
|
||||
CSGType op; McutMeshPtr mcutptr;
|
||||
explicit Frame(CSGType csgop = CSGType::Union)
|
||||
: op{ csgop }
|
||||
, mcutptr{ MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{}) }
|
||||
{}
|
||||
};
|
||||
|
||||
std::stack opstack{ std::vector<Frame>{} };
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<McutMeshPtr> McutMeshes = get_mcutptrs(ex_tbb, csgrange);
|
||||
|
||||
size_t csgidx = 0;
|
||||
for (auto& csgpart : csgrange) {
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
McutMeshPtr& mcutptr = McutMeshes[csgidx++];
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push(Frame{ op });
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame* top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->mcutptr, mcutptr);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
McutMeshPtr src = std::move(top->mcutptr);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
McutMeshPtr& dst = opstack.top().mcutptr;
|
||||
perform_csg(popop, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
mcutm = std::move(opstack.top().mcutptr);
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<class It, class Visitor>
|
||||
std::tuple<BooleanFailReason,std::string, It> check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
|
||||
{
|
||||
using namespace detail_cgal;
|
||||
BooleanFailReason fail_reason = BooleanFailReason::OK;
|
||||
std::string fail_part_name;
|
||||
std::vector<CGALMeshPtr> cgalmeshes(csgrange.size());
|
||||
auto check_part = [&csgrange, &cgalmeshes,&fail_reason,&fail_part_name](size_t i)
|
||||
{
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto &csgpart = *it;
|
||||
auto m = get_cgalmesh(csgpart);
|
||||
|
||||
// mesh can be nullptr if this is a stack push or pull
|
||||
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
|
||||
cgalmeshes[i] = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m || MeshBoolean::cgal::empty(*m)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " is empty, cannot do boolean!";
|
||||
fail_reason= BooleanFailReason::MeshEmpty;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*m)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh "<<i<<"/"<<csgrange.size()<<" does_bound_a_volume is false, cannot do boolean!";
|
||||
fail_reason= BooleanFailReason::NotBoundAVolume;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*m)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " does_self_intersect is true, cannot do boolean!";
|
||||
fail_reason= BooleanFailReason::SelfIntersect;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
cgalmeshes[i] = std::move(m);
|
||||
};
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
|
||||
|
||||
It ret = csgrange.end();
|
||||
for (size_t i = 0; i < csgrange.size(); ++i) {
|
||||
if (!cgalmeshes[i]) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
vfn(it);
|
||||
|
||||
if (ret == csgrange.end())
|
||||
ret = it;
|
||||
}
|
||||
}
|
||||
|
||||
return { fail_reason,fail_part_name, ret};
|
||||
}
|
||||
|
||||
template<class It>
|
||||
std::tuple<BooleanFailReason, std::string, It> check_csgmesh_booleans(const Range<It> &csgrange, bool use_mcut=false)
|
||||
{
|
||||
if(!use_mcut)
|
||||
return check_csgmesh_booleans(csgrange, [](auto &) {});
|
||||
else {
|
||||
using namespace detail_mcut;
|
||||
BooleanFailReason fail_reason = BooleanFailReason::OK;
|
||||
std::string fail_part_name;
|
||||
|
||||
std::vector<McutMeshPtr> McutMeshes(csgrange.size());
|
||||
auto check_part = [&csgrange, &McutMeshes,&fail_reason,&fail_part_name](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto& csgpart = *it;
|
||||
auto m = get_mcutmesh(csgpart);
|
||||
|
||||
// mesh can be nullptr if this is a stack push or pull
|
||||
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
|
||||
McutMeshes[i] = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m || MeshBoolean::mcut::empty(*m)) {
|
||||
fail_reason=BooleanFailReason::MeshEmpty;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
McutMeshes[i] = std::move(m);
|
||||
};
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
|
||||
return { fail_reason,fail_part_name, csgrange.end() };
|
||||
}
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range<It> &csgparts)
|
||||
{
|
||||
auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
|
||||
if (ret)
|
||||
perform_csgmesh_booleans_cgal(ret, csgparts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::mcut::McutMeshPtr perform_csgmesh_booleans_mcut(const Range<It>& csgparts)
|
||||
{
|
||||
auto ret = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{});
|
||||
if (ret)
|
||||
perform_csgmesh_booleans_mcut(ret, csgparts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace csg
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // PERFORMCSGMESHBOOLEANS_HPP
|
||||
@@ -0,0 +1,131 @@
|
||||
#ifndef SLICECSGMESH_HPP
|
||||
#define SLICECSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void merge_slices(csg::CSGType op, size_t i,
|
||||
std::vector<ExPolygons> &target,
|
||||
std::vector<ExPolygons> &source)
|
||||
{
|
||||
switch(op) {
|
||||
case CSGType::Union:
|
||||
for (ExPolygon &expoly : source[i])
|
||||
target[i].emplace_back(std::move(expoly));
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
target[i] = diff_ex(target[i], source[i]);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
target[i] = intersection_ex(target[i], source[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void collect_nonempty_indices(csg::CSGType op,
|
||||
const std::vector<float> &slicegrid,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
std::vector<size_t> &indices)
|
||||
{
|
||||
indices.clear();
|
||||
for (size_t i = 0; i < slicegrid.size(); ++i) {
|
||||
if (op == CSGType::Intersection || !slices[i].empty())
|
||||
indices.emplace_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<class ItCSG>
|
||||
std::vector<ExPolygons> slice_csgmesh_ex(
|
||||
const Range<ItCSG> &csgrange,
|
||||
const std::vector<float> &slicegrid,
|
||||
const MeshSlicingParamsEx ¶ms,
|
||||
const std::function<void()> &throw_on_cancel = [] {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
|
||||
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
MeshSlicingParamsEx params_cpy = params;
|
||||
auto trafo = params.trafo;
|
||||
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
|
||||
|
||||
opstack.push({CSGType::Union, std::vector<ExPolygons>(slicegrid.size())});
|
||||
|
||||
for (const auto &csgpart : csgrange) {
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push({op, std::vector<ExPolygons>(slicegrid.size())});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
if (its) {
|
||||
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
|
||||
std::vector<ExPolygons> slices = slice_mesh_ex(*its,
|
||||
slicegrid, params_cpy,
|
||||
throw_on_cancel);
|
||||
|
||||
assert(slices.size() == slicegrid.size());
|
||||
|
||||
collect_nonempty_indices(op, slicegrid, slices, nonempty_indices);
|
||||
|
||||
execution::for_each(
|
||||
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
|
||||
[op, &slices, &top](size_t i) {
|
||||
merge_slices(op, i, top->slices, slices);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
std::vector<ExPolygons> popslices = std::move(top->slices);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
std::vector<ExPolygons> &prev_slices = opstack.top().slices;
|
||||
|
||||
collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices);
|
||||
|
||||
execution::for_each(
|
||||
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
|
||||
[&popslices, &prev_slices, popop](size_t i) {
|
||||
merge_slices(popop, i, prev_slices, popslices);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> ret = std::move(opstack.top().slices);
|
||||
|
||||
// TODO: verify if this part can be omitted or not.
|
||||
execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) {
|
||||
auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){
|
||||
return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON);
|
||||
});
|
||||
|
||||
// Hopefully, ExPolygons are moved, not copied to new positions
|
||||
// and that is cheap for expolygons
|
||||
slice.erase(it, slice.end());
|
||||
slice = union_ex(slice);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // SLICECSGMESH_HPP
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef TRIANGLEMESHADAPTER_HPP
|
||||
#define TRIANGLEMESHADAPTER_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Provide default overloads for indexed_triangle_set to be usable as a plain
|
||||
// CSGPart with an implicit union operation
|
||||
|
||||
inline CSGType get_operation(const indexed_triangle_set &part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const indexed_triangle_set &part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part)
|
||||
{
|
||||
return ∂
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const indexed_triangle_set &part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const indexed_triangle_set *const part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part)
|
||||
{
|
||||
return part;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const indexed_triangle_set *const part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const TriangleMesh &part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const TriangleMesh &part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const TriangleMesh &part)
|
||||
{
|
||||
return &part.its;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const TriangleMesh &part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const TriangleMesh * const part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const TriangleMesh * const part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part)
|
||||
{
|
||||
return &part->its;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const TriangleMesh * const part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // TRIANGLEMESHADAPTER_HPP
|
||||
@@ -0,0 +1,116 @@
|
||||
#ifndef VOXELIZECSGMESH_HPP
|
||||
#define VOXELIZECSGMESH_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
#include "libslic3r/OpenVDBUtils.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
using VoxelizeParams = MeshToGridParams;
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params)
|
||||
{
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
VoxelGridPtr ret;
|
||||
|
||||
params.trafo(params.trafo() * csg::get_transform(csgpart));
|
||||
|
||||
if (its)
|
||||
ret = mesh_to_grid(*its, params);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src)
|
||||
{
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
if (is_grid_empty(*dst) && !is_grid_empty(*src))
|
||||
dst = clone(*src);
|
||||
else
|
||||
grid_union(*dst, *src);
|
||||
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
grid_difference(*dst, *src);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
grid_intersection(*dst, *src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<class It>
|
||||
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
|
||||
const VoxelizeParams ¶ms = {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
VoxelGridPtr ret;
|
||||
|
||||
std::vector<VoxelGridPtr> grids (csgrange.size());
|
||||
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) {
|
||||
if (params.statusfn() && params.statusfn()(-1))
|
||||
return;
|
||||
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, csgidx);
|
||||
auto &csgpart = *it;
|
||||
grids[csgidx] = get_voxelgrid(csgpart, params);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
|
||||
size_t csgidx = 0;
|
||||
struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; };
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
opstack.push({CSGType::Union, mesh_to_grid({}, params)});
|
||||
|
||||
for (auto &csgpart : csgrange) {
|
||||
if (params.statusfn() && params.statusfn()(-1))
|
||||
break;
|
||||
|
||||
auto &partgrid = grids[csgidx++];
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push({op, mesh_to_grid({}, params)});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->grid, partgrid);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
VoxelGridPtr popgrid = std::move(top->grid);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
VoxelGridPtr &grid = opstack.top().grid;
|
||||
perform_csg(popop, grid, popgrid);
|
||||
}
|
||||
}
|
||||
|
||||
ret = std::move(opstack.top().grid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // VOXELIZECSGMESH_HPP
|
||||
@@ -0,0 +1,101 @@
|
||||
#ifndef slic3r_Channel_hpp_
|
||||
#define slic3r_Channel_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
template<class T> class Channel
|
||||
{
|
||||
public:
|
||||
using UniqueLock = std::unique_lock<std::mutex>;
|
||||
|
||||
template<class Ptr> class Unlocker
|
||||
{
|
||||
public:
|
||||
Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {}
|
||||
Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {} // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move
|
||||
Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {}
|
||||
Unlocker& operator=(const Unlocker &other) = delete;
|
||||
Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); }
|
||||
|
||||
void operator()(Ptr*) { m_lock.unlock(); }
|
||||
private:
|
||||
mutable UniqueLock m_lock; // XXX: mutable: see above
|
||||
};
|
||||
|
||||
using Queue = std::deque<T>;
|
||||
using LockedConstPtr = std::unique_ptr<const Queue, Unlocker<const Queue>>;
|
||||
using LockedPtr = std::unique_ptr<Queue, Unlocker<Queue>>;
|
||||
|
||||
Channel() {}
|
||||
~Channel() {}
|
||||
|
||||
void push(const T& item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(item);
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
void push(T &&item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(std::forward<T>(item));
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
T pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_condition.wait(lock, [this]() { return !m_queue.empty(); });
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop_front();
|
||||
return item;
|
||||
}
|
||||
|
||||
boost::optional<T> try_pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
if (m_queue.empty()) {
|
||||
return boost::none;
|
||||
} else {
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlocked observer/hint. Thread unsafe! Keep in mind you need to re-verify the result after locking.
|
||||
size_t size_hint() const noexcept { return m_queue.size(); }
|
||||
|
||||
LockedConstPtr lock_read() const
|
||||
{
|
||||
return LockedConstPtr(&m_queue, Unlocker<const Queue>(UniqueLock(m_mutex)));
|
||||
}
|
||||
|
||||
LockedPtr lock_rw()
|
||||
{
|
||||
return LockedPtr(&m_queue, Unlocker<Queue>(UniqueLock(m_mutex)));
|
||||
}
|
||||
private:
|
||||
Queue m_queue;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Channel_hpp_
|
||||
@@ -0,0 +1,516 @@
|
||||
#include "Circle.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include "Geometry.hpp"
|
||||
|
||||
|
||||
//BBS: Refer to ArcWelderLib for the arc fitting functions
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//BBS: threshold used to judge collineation
|
||||
static const double Parallel_area_threshold = 0.0001;
|
||||
|
||||
bool Circle::try_create_circle(const Point& p1, const Point& p2, const Point& p3, const double max_radius, Circle& new_circle)
|
||||
{
|
||||
double x1 = p1.x();
|
||||
double y1 = p1.y();
|
||||
double x2 = p2.x();
|
||||
double y2 = p2.y();
|
||||
double x3 = p3.x();
|
||||
double y3 = p3.y();
|
||||
|
||||
//BBS: use area of triangle to judge whether three points are almostly on one line
|
||||
//Because the point is scale_ once, so area should scale_ twice.
|
||||
if (fabs((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) <= scale_(scale_(Parallel_area_threshold)))
|
||||
return false;
|
||||
|
||||
double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
|
||||
//BBS: take out to figure out how we handle very small values
|
||||
if (fabs(a) < SCALED_EPSILON)
|
||||
return false;
|
||||
|
||||
double b = (x1 * x1 + y1 * y1) * (y3 - y2)
|
||||
+ (x2 * x2 + y2 * y2) * (y1 - y3)
|
||||
+ (x3 * x3 + y3 * y3) * (y2 - y1);
|
||||
|
||||
double c = (x1 * x1 + y1 * y1) * (x2 - x3)
|
||||
+ (x2 * x2 + y2 * y2) * (x3 - x1)
|
||||
+ (x3 * x3 + y3 * y3) * (x1 - x2);
|
||||
|
||||
double center_x = -b / (2.0 * a);
|
||||
double center_y = -c / (2.0 * a);
|
||||
|
||||
double delta_x = center_x - x1;
|
||||
double delta_y = center_y - y1;
|
||||
double radius = sqrt(delta_x * delta_x + delta_y * delta_y);
|
||||
if (radius > max_radius)
|
||||
return false;
|
||||
|
||||
new_circle.center = Point(center_x, center_y);
|
||||
new_circle.radius = radius;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Circle::try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle)
|
||||
{
|
||||
size_t count = points.size();
|
||||
size_t middle_index = count / 2;
|
||||
// BBS: the middle point will almost always produce the best arcs with high possibility.
|
||||
if (count == 3) {
|
||||
return (Circle::try_create_circle(points[0], points[middle_index], points[count - 1], max_radius, new_circle)
|
||||
&& !new_circle.is_over_deviation(points, tolerance));
|
||||
} else {
|
||||
Point middle_point = (count % 2 == 0) ? (points[middle_index] + points[middle_index - 1]) / 2 :
|
||||
(points[middle_index - 1] + points[middle_index + 1]) / 2;
|
||||
if (Circle::try_create_circle(points[0], middle_point, points[count - 1], max_radius, new_circle)
|
||||
&& !new_circle.is_over_deviation(points, tolerance))
|
||||
return true;
|
||||
}
|
||||
|
||||
// BBS: Find the circle with the least deviation, if one exists.
|
||||
Circle test_circle;
|
||||
double least_deviation;
|
||||
bool found_circle = false;
|
||||
double current_deviation;
|
||||
for (int index = 1; index < count - 1; index++)
|
||||
{
|
||||
if (index == middle_index)
|
||||
// BBS: We already checked this one, and it failed. don't need to do again
|
||||
continue;
|
||||
|
||||
if (Circle::try_create_circle(points[0], points[index], points[count - 1], max_radius, test_circle) && test_circle.get_deviation_sum_squared(points, tolerance, current_deviation))
|
||||
{
|
||||
if (!found_circle || current_deviation < least_deviation)
|
||||
{
|
||||
found_circle = true;
|
||||
least_deviation = current_deviation;
|
||||
new_circle = test_circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found_circle;
|
||||
}
|
||||
|
||||
double Circle::get_polar_radians(const Point& p1) const
|
||||
{
|
||||
double polar_radians = atan2(p1.y() - center.y(), p1.x() - center.x());
|
||||
if (polar_radians < 0)
|
||||
polar_radians = (2.0 * PI) + polar_radians;
|
||||
return polar_radians;
|
||||
}
|
||||
|
||||
bool Circle::is_over_deviation(const Points& points, const double tolerance)
|
||||
{
|
||||
Point closest_point;
|
||||
Point temp;
|
||||
double distance_from_center;
|
||||
// BBS: skip the first and last points since they has fit perfectly.
|
||||
for (size_t index = 0; index < points.size() - 1; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
//BBS: check fitting tolerance
|
||||
temp = points[index] - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
if (std::fabs(distance_from_center - radius) > tolerance)
|
||||
return true;
|
||||
}
|
||||
|
||||
//BBS: Check the point perpendicular from the segment to the circle's center
|
||||
if (get_closest_perpendicular_point(points[index], points[(size_t)index + 1], center, closest_point)) {
|
||||
temp = closest_point - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
if (std::fabs(distance_from_center - radius) > tolerance)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Circle::get_closest_perpendicular_point(const Point& p1, const Point& p2, const Point& c, Point& out)
|
||||
{
|
||||
double x1 = p1.x();
|
||||
double y1 = p1.y();
|
||||
double x2 = p2.x();
|
||||
double y2 = p2.y();
|
||||
double x_dif = x2 - x1;
|
||||
double y_dif = y2 - y1;
|
||||
//BBS: [(Cx - Ax)(Bx - Ax) + (Cy - Ay)(By - Ay)] / [(Bx - Ax) ^ 2 + (By - Ay) ^ 2]
|
||||
double num = (c[0] - x1) * x_dif + (c[1] - y1) * y_dif;
|
||||
double denom = (x_dif * x_dif) + (y_dif * y_dif);
|
||||
double t = num / denom;
|
||||
|
||||
//BBS: Considering this a failure if t == 0 or t==1 within tolerance. In that case we hit the endpoint, which is OK.
|
||||
if (Circle::less_than_or_equal(t, 0) || Circle::greater_than_or_equal(t, 1))
|
||||
return false;
|
||||
|
||||
out[0] = x1 + t * (x2 - x1);
|
||||
out[1] = y1 + t * (y2 - y1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Circle::get_deviation_sum_squared(const Points& points, const double tolerance, double& total_deviation)
|
||||
{
|
||||
total_deviation = 0;
|
||||
Point temp;
|
||||
double distance_from_center, deviation;
|
||||
// BBS: skip the first and last points since they are on the circle
|
||||
for (int index = 1; index < points.size() - 1; index++)
|
||||
{
|
||||
//BBS: make sure the length from the center of our circle to the test point is
|
||||
// at or below our max distance.
|
||||
temp = points[index] - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
deviation = std::fabs(distance_from_center - radius);
|
||||
total_deviation += deviation * deviation;
|
||||
if (deviation > tolerance)
|
||||
return false;
|
||||
|
||||
}
|
||||
Point closest_point;
|
||||
//BBS: check the point perpendicular from the segment to the circle's center
|
||||
for (int index = 0; index < points.size() - 1; index++)
|
||||
{
|
||||
if (get_closest_perpendicular_point(points[index], points[(size_t)index + 1], center, closest_point)) {
|
||||
temp = closest_point - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
deviation = std::fabs(distance_from_center - radius);
|
||||
total_deviation += deviation * deviation;
|
||||
if (deviation > tolerance)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//BBS: only support calculate on X-Y plane, Z is useless
|
||||
Vec3f Circle::calc_tangential_vector(const Vec3f& pos, const Vec3f& center_pos, const bool is_ccw)
|
||||
{
|
||||
Vec3f dir = center_pos - pos;
|
||||
dir(2,0) = 0;
|
||||
dir.normalize();
|
||||
Vec3f res;
|
||||
if (is_ccw)
|
||||
res = { dir(1, 0), -dir(0, 0), 0.0f };
|
||||
else
|
||||
res = { -dir(1, 0), dir(0, 0), 0.0f };
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ArcSegment::reverse()
|
||||
{
|
||||
if (!is_valid())
|
||||
return false;
|
||||
std::swap(start_point, end_point);
|
||||
direction = (direction == ArcDirection::Arc_Dir_CCW) ? ArcDirection::Arc_Dir_CW : ArcDirection::Arc_Dir_CCW;
|
||||
angle_radians *= -1.0;
|
||||
std::swap(polar_start_theta, polar_end_theta);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::clip_start(const Point &point)
|
||||
{
|
||||
if (!is_valid() || point == center || !is_point_inside(point))
|
||||
return false;
|
||||
start_point = get_closest_point(point);
|
||||
update_angle_and_length();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::clip_end(const Point &point)
|
||||
{
|
||||
if (!is_valid() || point == center || !is_point_inside(point))
|
||||
return false;
|
||||
end_point = get_closest_point(point);
|
||||
update_angle_and_length();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::split_at(const Point &point, ArcSegment& p1, ArcSegment& p2)
|
||||
{
|
||||
if (!is_valid() || point == center || !is_point_inside(point))
|
||||
return false;
|
||||
Point segment_point = get_closest_point(point);
|
||||
p1 = ArcSegment(center, radius, this->start_point, segment_point, this->direction);
|
||||
p2 = ArcSegment(center, radius, segment_point, this->end_point, this->direction);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::is_point_inside(const Point& point) const
|
||||
{
|
||||
double polar_theta = get_polar_radians(point);
|
||||
double radian_delta = polar_theta - polar_start_theta;
|
||||
if (radian_delta > 0 && direction == ArcDirection::Arc_Dir_CW)
|
||||
radian_delta = radian_delta - 2 * M_PI;
|
||||
else if (radian_delta < 0 && direction == ArcDirection::Arc_Dir_CCW)
|
||||
radian_delta = radian_delta + 2 * M_PI;
|
||||
|
||||
return (direction == ArcDirection::Arc_Dir_CCW ?
|
||||
radian_delta > 0.0 && radian_delta < angle_radians :
|
||||
radian_delta < 0.0 && radian_delta > angle_radians);
|
||||
}
|
||||
|
||||
void ArcSegment::update_angle_and_length()
|
||||
{
|
||||
polar_start_theta = get_polar_radians(start_point);
|
||||
polar_end_theta = get_polar_radians(end_point);
|
||||
angle_radians = polar_end_theta - polar_start_theta;
|
||||
if (angle_radians < 0 && direction == ArcDirection::Arc_Dir_CCW)
|
||||
angle_radians = angle_radians + 2 * M_PI;
|
||||
else if (angle_radians > 0 && direction == ArcDirection::Arc_Dir_CW)
|
||||
angle_radians = angle_radians - 2 * M_PI;
|
||||
length = fabs(angle_radians) * radius;
|
||||
is_arc = true;
|
||||
}
|
||||
|
||||
bool ArcSegment::try_create_arc(
|
||||
const Points& points,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double max_radius,
|
||||
double tolerance,
|
||||
double path_tolerance_percent)
|
||||
{
|
||||
Circle test_circle = (Circle)target_arc;
|
||||
if (!Circle::try_create_circle(points, max_radius, tolerance, test_circle))
|
||||
return false;
|
||||
|
||||
int mid_point_index = ((points.size() - 2) / 2) + 1;
|
||||
ArcSegment test_arc;
|
||||
if (!ArcSegment::try_create_arc(test_circle, points[0], points[mid_point_index], points[points.size() - 1], test_arc, approximate_length, path_tolerance_percent))
|
||||
return false;
|
||||
|
||||
if (ArcSegment::are_points_within_slice(test_arc, points))
|
||||
{
|
||||
target_arc = test_arc;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArcSegment::try_create_arc(
|
||||
const Circle& c,
|
||||
const Point& start_point,
|
||||
const Point& mid_point,
|
||||
const Point& end_point,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double path_tolerance_percent)
|
||||
{
|
||||
double polar_start_theta = c.get_polar_radians(start_point);
|
||||
double polar_mid_theta = c.get_polar_radians(mid_point);
|
||||
double polar_end_theta = c.get_polar_radians(end_point);
|
||||
|
||||
double angle_radians = 0;
|
||||
ArcDirection direction = ArcDirection::Arc_Dir_unknow;
|
||||
//BBS: calculate the direction of the arc
|
||||
if (polar_end_theta > polar_start_theta) {
|
||||
if (polar_start_theta < polar_mid_theta && polar_mid_theta < polar_end_theta) {
|
||||
direction = ArcDirection::Arc_Dir_CCW;
|
||||
angle_radians = polar_end_theta - polar_start_theta;
|
||||
} else if ((0.0 <= polar_mid_theta && polar_mid_theta < polar_start_theta) ||
|
||||
(polar_end_theta < polar_mid_theta && polar_mid_theta < (2.0 * PI))) {
|
||||
direction = ArcDirection::Arc_Dir_CW;
|
||||
angle_radians = polar_start_theta + ((2.0 * PI) - polar_end_theta);
|
||||
}
|
||||
} else if (polar_start_theta > polar_end_theta) {
|
||||
if ((polar_start_theta < polar_mid_theta && polar_mid_theta < (2.0 * PI)) ||
|
||||
(0.0 < polar_mid_theta && polar_mid_theta < polar_end_theta)) {
|
||||
direction = ArcDirection::Arc_Dir_CCW;
|
||||
angle_radians = polar_end_theta + ((2.0 * PI) - polar_start_theta);
|
||||
} else if (polar_end_theta < polar_mid_theta && polar_mid_theta < polar_start_theta) {
|
||||
direction = ArcDirection::Arc_Dir_CW;
|
||||
angle_radians = polar_start_theta - polar_end_theta;
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: this doesn't always work.. in rare situations, the angle may be backward
|
||||
if (direction == ArcDirection::Arc_Dir_unknow || std::fabs(angle_radians) < EPSILON)
|
||||
return false;
|
||||
|
||||
// BBS: Check the length against the original length.
|
||||
// This can trigger simply due to the differing path lengths
|
||||
// but also could indicate that the vector calculation above
|
||||
// got wrong direction
|
||||
double arc_length = c.radius * angle_radians;
|
||||
double difference = (arc_length - approximate_length) / approximate_length;
|
||||
if (std::fabs(difference) >= path_tolerance_percent)
|
||||
{
|
||||
// BBS: So it's possible that vector calculation above got wrong direction.
|
||||
// This can happen if there is a crazy arrangement of points
|
||||
// extremely close to eachother. They have to be close enough to
|
||||
// break our other checks. However, we may be able to salvage this.
|
||||
// see if an arc moving in the opposite direction had the correct length.
|
||||
|
||||
//BBS: Find the rest of the angle across the circle
|
||||
double test_radians = std::fabs(angle_radians - 2 * PI);
|
||||
// Calculate the length of that arc
|
||||
double test_arc_length = c.radius * test_radians;
|
||||
difference = (test_arc_length - approximate_length) / approximate_length;
|
||||
if (std::fabs(difference) >= path_tolerance_percent)
|
||||
return false;
|
||||
//BBS: Set the new length and flip the direction (but not the angle)!
|
||||
arc_length = test_arc_length;
|
||||
direction = direction == ArcDirection::Arc_Dir_CCW ? ArcDirection::Arc_Dir_CW : ArcDirection::Arc_Dir_CCW;
|
||||
}
|
||||
|
||||
if (direction == ArcDirection::Arc_Dir_CW)
|
||||
angle_radians *= -1.0;
|
||||
|
||||
target_arc.is_arc = true;
|
||||
target_arc.direction = direction;
|
||||
target_arc.center = c.center;
|
||||
target_arc.radius = c.radius;
|
||||
target_arc.start_point = start_point;
|
||||
target_arc.end_point = end_point;
|
||||
target_arc.length = arc_length;
|
||||
target_arc.angle_radians = angle_radians;
|
||||
target_arc.polar_start_theta = polar_start_theta;
|
||||
target_arc.polar_end_theta = polar_end_theta;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Points& points)
|
||||
{
|
||||
//BBS: Check all the points and see if they fit inside of the angles
|
||||
double previous_polar = test_arc.polar_start_theta;
|
||||
bool will_cross_zero = false;
|
||||
bool crossed_zero = false;
|
||||
const int point_count = points.size();
|
||||
|
||||
Vec2d start_norm(((double)test_arc.start_point.x() - (double)test_arc.center.x()) / test_arc.radius,
|
||||
((double)test_arc.start_point.y() - (double)test_arc.center.y()) / test_arc.radius);
|
||||
Vec2d end_norm(((double)test_arc.end_point.x() - (double)test_arc.center.x()) / test_arc.radius,
|
||||
((double)test_arc.end_point.y() - (double)test_arc.center.y()) / test_arc.radius);
|
||||
|
||||
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
||||
will_cross_zero = test_arc.polar_start_theta > test_arc.polar_end_theta;
|
||||
else
|
||||
will_cross_zero = test_arc.polar_start_theta < test_arc.polar_end_theta;
|
||||
|
||||
//BBS: check if point 1 to point 2 cross zero
|
||||
double polar_test;
|
||||
for (int index = point_count - 2; index < point_count; index++)
|
||||
{
|
||||
if (index < point_count - 1)
|
||||
polar_test = test_arc.get_polar_radians(points[index]);
|
||||
else
|
||||
polar_test = test_arc.polar_end_theta;
|
||||
|
||||
//BBS: First ensure the test point is within the arc
|
||||
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
||||
{
|
||||
//BBS: Only check to see if we are within the arc if this isn't the endpoint
|
||||
if (index < point_count - 1) {
|
||||
if (will_cross_zero) {
|
||||
if (!(polar_test > test_arc.polar_start_theta || polar_test < test_arc.polar_end_theta))
|
||||
return false;
|
||||
} else if (!(test_arc.polar_start_theta < polar_test && polar_test < test_arc.polar_end_theta))
|
||||
return false;
|
||||
}
|
||||
//BBS: check the angles are increasing
|
||||
if (previous_polar > polar_test) {
|
||||
if (!will_cross_zero)
|
||||
return false;
|
||||
|
||||
//BBS: Allow the angle to cross zero once
|
||||
if (crossed_zero)
|
||||
return false;
|
||||
crossed_zero = true;
|
||||
}
|
||||
} else {
|
||||
if (index < point_count - 1) {
|
||||
if (will_cross_zero) {
|
||||
if (!(polar_test < test_arc.polar_start_theta || polar_test > test_arc.polar_end_theta))
|
||||
return false;
|
||||
} else if (!(test_arc.polar_start_theta > polar_test && polar_test > test_arc.polar_end_theta))
|
||||
return false;
|
||||
}
|
||||
//BBS: Now make sure the angles are decreasing
|
||||
if (previous_polar < polar_test)
|
||||
{
|
||||
if (!will_cross_zero)
|
||||
return false;
|
||||
//BBS: Allow the angle to cross zero once
|
||||
if (crossed_zero)
|
||||
return false;
|
||||
crossed_zero = true;
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: check if the segment intersects either of the vector from the center of the circle to the endpoints of the arc
|
||||
Line segmemt(points[index - 1], points[index]);
|
||||
if ((index != 1 && ray_intersects_segment(test_arc.center, start_norm, segmemt)) ||
|
||||
(index != point_count - 1 && ray_intersects_segment(test_arc.center, end_norm, segmemt)))
|
||||
return false;
|
||||
previous_polar = polar_test;
|
||||
}
|
||||
//BBS: Ensure that all arcs that cross zero
|
||||
if (will_cross_zero != crossed_zero)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// BBS: this function is used to detect whether a ray cross the segment
|
||||
bool ArcSegment::ray_intersects_segment(const Point &rayOrigin, const Vec2d &rayDirection, const Line& segment)
|
||||
{
|
||||
Vec2d v1 = Vec2d(rayOrigin.x() - segment.a.x(), rayOrigin.y() - segment.a.y());
|
||||
Vec2d v2 = Vec2d(segment.b.x() - segment.a.x(), segment.b.y() - segment.a.y());
|
||||
Vec2d v3 = Vec2d(-rayDirection(1), rayDirection(0));
|
||||
|
||||
double dot = v2(0) * v3(0) + v2(1) * v3(1);
|
||||
if (std::fabs(dot) < SCALED_EPSILON)
|
||||
return false;
|
||||
|
||||
double t1 = (v2(0) * v1(1) - v2(1) * v1(0)) / dot;
|
||||
double t2 = (v1(0) * v3(0) + v1(1) * v3(1)) / dot;
|
||||
|
||||
if (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// BBS: new function to calculate arc radian in X-Y plane
|
||||
float ArcSegment::calc_arc_radian(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
||||
{
|
||||
Vec3f delta1 = center_pos - start_pos;
|
||||
Vec3f delta2 = center_pos - end_pos;
|
||||
// only consider arc in x-y plane, so clean z distance
|
||||
delta1(2,0) = 0;
|
||||
delta2(2,0) = 0;
|
||||
|
||||
float radian;
|
||||
if ((delta1 - delta2).norm() < 1e-6) {
|
||||
// start_pos is same with end_pos, we think it's a full circle
|
||||
radian = 2 * M_PI;
|
||||
} else {
|
||||
double dot = delta1.dot(delta2);
|
||||
double cross = (double)delta1(0, 0) * (double)delta2(1, 0) - (double)delta1(1, 0) * (double)delta2(0, 0);
|
||||
radian = atan2(cross, dot);
|
||||
if (is_ccw)
|
||||
radian = (radian < 0) ? 2 * M_PI + radian : radian;
|
||||
else
|
||||
radian = (radian < 0) ? abs(radian) : 2 * M_PI - radian;
|
||||
}
|
||||
return radian;
|
||||
}
|
||||
|
||||
float ArcSegment::calc_arc_radius(Vec3f start_pos, Vec3f center_pos)
|
||||
{
|
||||
Vec3f delta1 = center_pos - start_pos;
|
||||
delta1(2,0) = 0;
|
||||
return delta1.norm();
|
||||
}
|
||||
|
||||
// BBS: new function to calculate arc length in X-Y plane
|
||||
float ArcSegment::calc_arc_length(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
||||
{
|
||||
return calc_arc_radius(start_pos, center_pos) * calc_arc_radian(start_pos, end_pos, center_pos, is_ccw);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
#ifndef slic3r_Circle_hpp_
|
||||
#define slic3r_Circle_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
constexpr double ZERO_TOLERANCE = 0.000005;
|
||||
|
||||
class Circle {
|
||||
public:
|
||||
Circle() {
|
||||
center = Point(0,0);
|
||||
radius = 0;
|
||||
}
|
||||
Circle(Point &p, double r) {
|
||||
center = p;
|
||||
radius = r;
|
||||
}
|
||||
Point center;
|
||||
double radius;
|
||||
|
||||
Point get_closest_point(const Point& input) {
|
||||
Vec2d v = (input - center).cast<double>().normalized();
|
||||
return (center + (v * radius).cast<coord_t>());
|
||||
}
|
||||
|
||||
static bool try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius, Circle& new_circle);
|
||||
static bool try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle);
|
||||
double get_polar_radians(const Point& p1) const;
|
||||
bool is_over_deviation(const Points& points, const double tolerance);
|
||||
bool get_deviation_sum_squared(const Points& points, const double tolerance, double& sum_deviation);
|
||||
|
||||
//BBS: only support calculate on X-Y plane, Z is useless
|
||||
static Vec3f calc_tangential_vector(const Vec3f& pos, const Vec3f& center_pos, const bool is_ccw);
|
||||
static bool get_closest_perpendicular_point(const Point& p1, const Point& p2, const Point& c, Point& out);
|
||||
static bool is_equal(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
double abs_difference = std::fabs(x - y);
|
||||
return abs_difference < tolerance;
|
||||
};
|
||||
static bool greater_than(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
return x > y && !Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
static bool greater_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
return x > y || Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
static bool less_than(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
return x < y && !Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
static bool less_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE){
|
||||
return x < y || Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
enum class ArcDirection : unsigned char {
|
||||
Arc_Dir_unknow,
|
||||
Arc_Dir_CCW,
|
||||
Arc_Dir_CW,
|
||||
Count
|
||||
};
|
||||
|
||||
#define DEFAULT_SCALED_MAX_RADIUS scale_(2000) // 2000mm
|
||||
#define DEFAULT_SCALED_RESOLUTION scale_(0.05) // 0.05mm
|
||||
#define DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE 0.05 // 5 percent
|
||||
|
||||
class ArcSegment: public Circle {
|
||||
public:
|
||||
ArcSegment(): Circle() {}
|
||||
ArcSegment(Point center, double radius, Point start, Point end, ArcDirection dir) :
|
||||
Circle(center, radius),
|
||||
start_point(start),
|
||||
end_point(end),
|
||||
direction(dir) {
|
||||
if (radius == 0.0 ||
|
||||
start_point == center ||
|
||||
end_point == center ||
|
||||
start_point == end_point) {
|
||||
is_arc = false;
|
||||
return;
|
||||
}
|
||||
update_angle_and_length();
|
||||
is_arc = true;
|
||||
}
|
||||
|
||||
bool is_arc = false;
|
||||
double length = 0;
|
||||
double angle_radians = 0;
|
||||
double polar_start_theta = 0;
|
||||
double polar_end_theta = 0;
|
||||
Point start_point { Point(0,0) };
|
||||
Point end_point{ Point(0,0) };
|
||||
ArcDirection direction = ArcDirection::Arc_Dir_unknow;
|
||||
|
||||
bool is_valid() const { return is_arc; }
|
||||
bool clip_start(const Point& point);
|
||||
bool clip_end(const Point& point);
|
||||
bool reverse();
|
||||
bool split_at(const Point& point, ArcSegment& p1, ArcSegment& p2);
|
||||
bool is_point_inside(const Point& point) const;
|
||||
|
||||
private:
|
||||
void update_angle_and_length();
|
||||
|
||||
public:
|
||||
static bool try_create_arc(
|
||||
const Points &points,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double max_radius = DEFAULT_SCALED_MAX_RADIUS,
|
||||
double tolerance = DEFAULT_SCALED_RESOLUTION,
|
||||
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
|
||||
|
||||
static bool are_points_within_slice(const ArcSegment& test_arc, const Points &points);
|
||||
// BBS: this function is used to detect whether a ray cross the segment
|
||||
static bool ray_intersects_segment(const Point& rayOrigin, const Vec2d& rayDirection, const Line& segment);
|
||||
// BBS: these three functions are used to calculate related arguments of arc in unscale_field.
|
||||
static float calc_arc_radian(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw);
|
||||
static float calc_arc_radius(Vec3f start_pos, Vec3f center_pos);
|
||||
static float calc_arc_length(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw);
|
||||
private:
|
||||
static bool try_create_arc(
|
||||
const Circle& c,
|
||||
const Point& start_point,
|
||||
const Point& mid_point,
|
||||
const Point& end_point,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user