1
0
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:
Jake
2026-05-11 19:29:55 +01:00
commit d4d1215874
16967 changed files with 4075897 additions and 0 deletions
+376
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+142
View File
@@ -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
+310
View File
@@ -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());
}
}
+393
View File
@@ -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);
}
+31
View File
@@ -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)
+50
View File
@@ -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;
}
+541
View File
@@ -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;
}
}
+284
View File
@@ -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
+119
View File
@@ -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"
+139
View File
@@ -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
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
#cmakedefine SLIC3R_FHS @SLIC3R_FHS@
#define SLIC3R_FHS_RESOURCES "@SLIC3R_FHS_RESOURCES@"
+3
View File
@@ -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})
+311
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+322
View File
@@ -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
+142
View File
@@ -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
+992
View File
@@ -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 RayBox 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_ */
+373
View File
@@ -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_ */
+157
View File
@@ -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
+317
View File
@@ -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
+70
View File
@@ -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_ */
+563
View File
@@ -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 &params)
{
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 &params)
{
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 &params)
{
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 &params)
{
// 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
+118
View File
@@ -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 &params);
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters &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);
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 &params);
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 &params);
} // Algorithm
} // Slic3r
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */
+130
View File
@@ -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
+423
View File
@@ -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 &section, 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 &section, 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 &section, 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 &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_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 &section, 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 &section, 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 &section) const
{ return m_storage.find(section) != m_storage.end(); }
const std::map<std::string, std::string>& get_section(const std::string &section) const
{ return m_storage.find(section)->second; }
void set_section(const std::string &section, const std::map<std::string, std::string>& data)
{ m_storage[section] = data; }
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
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
+879
View File
@@ -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 &params)
: 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
+144
View File
@@ -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 &params);
/*!
* 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
+39
View File
@@ -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
+132
View File
@@ -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
+150
View File
@@ -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;
}
+113
View File
@@ -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
+153
View File
@@ -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;
}
}
}
}
+53
View File
@@ -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
+219
View File
@@ -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 &params = {});
// 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 &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
}} // namespace Slic3r::arrangement
#endif // MODELARRANGE_HPP
+84
View File
@@ -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
+46
View File
@@ -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_
+287
View File
@@ -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 &center) 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);
}
}
+308
View File
@@ -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 &center) const;
void rotate(double angle) { (*this) = this->rotated(angle); }
void rotate(double angle, const Point &center) { (*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
+474
View File
@@ -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;
}
}
+131
View File
@@ -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
+990
View File
@@ -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 &center_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
+29
View File
@@ -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_
+68
View File
@@ -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
+594
View File
@@ -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
+166
View File
@@ -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_
+636
View File
@@ -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 ()
+121
View File
@@ -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
+80
View File
@@ -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
+92
View File
@@ -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
+131
View File
@@ -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 &params,
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 &part;
}
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
+116
View File
@@ -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 &params = {})
{
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
+101
View File
@@ -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_
+516
View File
@@ -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);
}
}
+135
View File
@@ -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