mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 09:11:08 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d218f9abf | ||
|
|
4a20168742 | ||
|
|
1466c0e57f | ||
|
|
cd02116242 | ||
|
|
35bac4cb69 | ||
|
|
0f3e8fbf27 | ||
|
|
b5ef24e7ff | ||
|
|
8585eae816 | ||
|
|
e72ace164b | ||
|
|
b5f5b50157 | ||
|
|
e0b35f9ec9 | ||
|
|
eb28daf0fe | ||
|
|
6ac7ae24a3 | ||
|
|
d55e32fed4 | ||
|
|
cd0b529e31 |
@@ -707,12 +707,9 @@ jobs:
|
|||||||
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||||
- name: Deploy Ubuntu release
|
- name: Deploy Ubuntu release
|
||||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||||
uses: rickstaa/action-create-tag@v1
|
run: |
|
||||||
with:
|
git -c user.name="${GITHUB_ACTOR}" -c user.email="${GITHUB_ACTOR}@users.noreply.github.com" tag -f -a nightly-builds "${GITHUB_SHA}" -m nightly-builds
|
||||||
tag: "nightly-builds"
|
git push -f origin refs/tags/nightly-builds
|
||||||
tag_exists_error: false
|
|
||||||
force_push_tag: true
|
|
||||||
message: "nightly-builds"
|
|
||||||
|
|
||||||
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
|
- name: Deploy Ubuntu OrcaSlicer_profile_validator release
|
||||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y --no-install-recommends \
|
sudo apt-get install -y --no-install-recommends \
|
||||||
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
|
libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
@@ -224,7 +224,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y --no-install-recommends \
|
sudo apt-get install -y --no-install-recommends \
|
||||||
xvfb xdotool imagemagick openbox mesa-utils \
|
xvfb xdotool imagemagick openbox mesa-utils \
|
||||||
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
|
libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
|
||||||
|
|
||||||
- name: Run the parity harness
|
- name: Run the parity harness
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,359 +0,0 @@
|
|||||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
||||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
|
||||||
|
|
||||||
# PrusaSlicer specifics:
|
|
||||||
# This file is backported from CMake 3.15 distribution to behave uniformly
|
|
||||||
# across all versions of CMake. It explicitly adds GLEW_STATIC compile
|
|
||||||
# definition to static targets which is needed to prevent link errors.
|
|
||||||
|
|
||||||
#[=======================================================================[.rst:
|
|
||||||
FindGLEW
|
|
||||||
--------
|
|
||||||
|
|
||||||
Find the OpenGL Extension Wrangler Library (GLEW)
|
|
||||||
|
|
||||||
Input Variables
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The following variables may be set to influence this module’s behavior:
|
|
||||||
|
|
||||||
``GLEW_USE_STATIC_LIBS``
|
|
||||||
to find and create :prop_tgt:`IMPORTED` target for static linkage.
|
|
||||||
|
|
||||||
``GLEW_VERBOSE``
|
|
||||||
to output a detailed log of this module.
|
|
||||||
|
|
||||||
Imported Targets
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
This module defines the following :ref:`Imported Targets <Imported Targets>`:
|
|
||||||
|
|
||||||
|
|
||||||
``GLEW::glew``
|
|
||||||
The GLEW shared library.
|
|
||||||
``GLEW::glew_s``
|
|
||||||
The GLEW static library, if ``GLEW_USE_STATIC_LIBS`` is set to ``TRUE``.
|
|
||||||
``GLEW::GLEW``
|
|
||||||
Duplicates either ``GLEW::glew`` or ``GLEW::glew_s`` based on availability.
|
|
||||||
|
|
||||||
Result Variables
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
This module defines the following variables:
|
|
||||||
|
|
||||||
``GLEW_INCLUDE_DIRS``
|
|
||||||
include directories for GLEW
|
|
||||||
``GLEW_LIBRARIES``
|
|
||||||
libraries to link against GLEW
|
|
||||||
``GLEW_SHARED_LIBRARIES``
|
|
||||||
libraries to link against shared GLEW
|
|
||||||
``GLEW_STATIC_LIBRARIES``
|
|
||||||
libraries to link against static GLEW
|
|
||||||
``GLEW_FOUND``
|
|
||||||
true if GLEW has been found and can be used
|
|
||||||
``GLEW_VERSION``
|
|
||||||
GLEW version
|
|
||||||
``GLEW_VERSION_MAJOR``
|
|
||||||
GLEW major version
|
|
||||||
``GLEW_VERSION_MINOR``
|
|
||||||
GLEW minor version
|
|
||||||
``GLEW_VERSION_MICRO``
|
|
||||||
GLEW micro version
|
|
||||||
|
|
||||||
#]=======================================================================]
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
find_package(OpenGL QUIET)
|
|
||||||
|
|
||||||
if(OpenGL_FOUND)
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: Found OpenGL Framework.")
|
|
||||||
message(STATUS "FindGLEW: OPENGL_LIBRARIES: ${OPENGL_LIBRARIES}")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: could not find GLEW library.")
|
|
||||||
endif()
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
function(__glew_set_find_library_suffix shared_or_static)
|
|
||||||
if((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "SHARED")
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so")
|
|
||||||
elseif((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "STATIC")
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
|
||||||
elseif(APPLE AND "${shared_or_static}" MATCHES "SHARED")
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib;.so")
|
|
||||||
elseif(APPLE AND "${shared_or_static}" MATCHES "STATIC")
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
|
||||||
elseif(WIN32 AND "${shared_or_static}" MATCHES "SHARED")
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib")
|
|
||||||
elseif(WIN32 AND "${shared_or_static}" MATCHES "STATIC")
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib;.a;.dll.a")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}" PARENT_SCOPE)
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: CMAKE_FIND_LIBRARY_SUFFIXES for ${shared_or_static}: ${CMAKE_FIND_LIBRARY_SUFFIXES}")
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
if(DEFINED GLEW_USE_STATIC_LIBS)
|
|
||||||
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS: ${GLEW_USE_STATIC_LIBS}.")
|
|
||||||
else()
|
|
||||||
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS is undefined. Treated as FALSE.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_path(GLEW_INCLUDE_DIR GL/glew.h)
|
|
||||||
mark_as_advanced(GLEW_INCLUDE_DIR)
|
|
||||||
|
|
||||||
set(GLEW_INCLUDE_DIRS ${GLEW_INCLUDE_DIR})
|
|
||||||
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: GLEW_INCLUDE_DIR: ${GLEW_INCLUDE_DIR}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_INCLUDE_DIRS: ${GLEW_INCLUDE_DIRS}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64" OR "${CMAKE_GENERATOR}" MATCHES "Win64")
|
|
||||||
set(_arch "x64")
|
|
||||||
elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "ARM64")
|
|
||||||
set(_arch "x64") # GLEW ships one header set; ARM64 uses the x64 import path
|
|
||||||
else()
|
|
||||||
set(_arch "Win32")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
set(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
|
||||||
|
|
||||||
__glew_set_find_library_suffix(SHARED)
|
|
||||||
|
|
||||||
find_library(GLEW_SHARED_LIBRARY_RELEASE
|
|
||||||
NAMES GLEW glew glew32
|
|
||||||
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
|
|
||||||
PATHS ENV GLEW_ROOT)
|
|
||||||
|
|
||||||
find_library(GLEW_SHARED_LIBRARY_DEBUG
|
|
||||||
NAMES GLEWd glewd glew32d
|
|
||||||
PATH_SUFFIXES lib lib64
|
|
||||||
PATHS ENV GLEW_ROOT)
|
|
||||||
|
|
||||||
|
|
||||||
__glew_set_find_library_suffix(STATIC)
|
|
||||||
|
|
||||||
find_library(GLEW_STATIC_LIBRARY_RELEASE
|
|
||||||
NAMES GLEW glew glew32s
|
|
||||||
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
|
|
||||||
PATHS ENV GLEW_ROOT)
|
|
||||||
|
|
||||||
find_library(GLEW_STATIC_LIBRARY_DEBUG
|
|
||||||
NAMES GLEWds GLEWd glewd glewds glew32ds
|
|
||||||
PATH_SUFFIXES lib lib64
|
|
||||||
PATHS ENV GLEW_ROOT)
|
|
||||||
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ${__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES})
|
|
||||||
unset(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES)
|
|
||||||
|
|
||||||
include(SelectLibraryConfigurations)
|
|
||||||
|
|
||||||
select_library_configurations(GLEW_SHARED)
|
|
||||||
select_library_configurations(GLEW_STATIC)
|
|
||||||
|
|
||||||
if(NOT GLEW_USE_STATIC_LIBS)
|
|
||||||
set(GLEW_LIBRARIES ${GLEW_SHARED_LIBRARY})
|
|
||||||
else()
|
|
||||||
set(GLEW_LIBRARIES ${GLEW_STATIC_LIBRARY})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_RELEASE: ${GLEW_SHARED_LIBRARY_RELEASE}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_RELEASE: ${GLEW_STATIC_LIBRARY_RELEASE}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_DEBUG: ${GLEW_SHARED_LIBRARY_DEBUG}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_DEBUG: ${GLEW_STATIC_LIBRARY_DEBUG}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY: ${GLEW_SHARED_LIBRARY}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY: ${GLEW_STATIC_LIBRARY}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_LIBRARIES: ${GLEW_LIBRARIES}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
# Read version from GL/glew.h file
|
|
||||||
if(EXISTS "${GLEW_INCLUDE_DIR}/GL/glew.h")
|
|
||||||
file(STRINGS "${GLEW_INCLUDE_DIR}/GL/glew.h" _contents REGEX "^VERSION_.+ [0-9]+")
|
|
||||||
if(_contents)
|
|
||||||
string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MAJOR "${_contents}")
|
|
||||||
string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MINOR "${_contents}")
|
|
||||||
string(REGEX REPLACE ".*VERSION_MICRO[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MICRO "${_contents}")
|
|
||||||
set(GLEW_VERSION "${GLEW_VERSION_MAJOR}.${GLEW_VERSION_MINOR}.${GLEW_VERSION_MICRO}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: GLEW_VERSION_MAJOR: ${GLEW_VERSION_MAJOR}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_VERSION_MINOR: ${GLEW_VERSION_MINOR}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_VERSION_MICRO: ${GLEW_VERSION_MICRO}")
|
|
||||||
message(STATUS "FindGLEW: GLEW_VERSION: ${GLEW_VERSION}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package_handle_standard_args(GLEW
|
|
||||||
REQUIRED_VARS GLEW_INCLUDE_DIRS GLEW_LIBRARIES
|
|
||||||
VERSION_VAR GLEW_VERSION)
|
|
||||||
|
|
||||||
if(NOT GLEW_FOUND)
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: could not find GLEW library.")
|
|
||||||
endif()
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
if(NOT TARGET GLEW::glew AND NOT GLEW_USE_STATIC_LIBS)
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: Creating GLEW::glew imported target.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(GLEW::glew UNKNOWN IMPORTED)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew
|
|
||||||
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
|
|
||||||
set_target_properties(GLEW::glew
|
|
||||||
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
|
|
||||||
else()
|
|
||||||
set_target_properties(GLEW::glew
|
|
||||||
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_SHARED_LIBRARY_RELEASE)
|
|
||||||
set_property(TARGET GLEW::glew
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew
|
|
||||||
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_SHARED_LIBRARY_DEBUG)
|
|
||||||
set_property(TARGET GLEW::glew
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew
|
|
||||||
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
elseif(NOT TARGET GLEW::glew_s AND GLEW_USE_STATIC_LIBS)
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: Creating GLEW::glew_s imported target.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(GLEW::glew_s UNKNOWN IMPORTED)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew_s
|
|
||||||
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew_s PROPERTIES INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
|
|
||||||
set_target_properties(GLEW::glew_s
|
|
||||||
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
|
|
||||||
else()
|
|
||||||
set_target_properties(GLEW::glew_s
|
|
||||||
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_STATIC_LIBRARY_RELEASE)
|
|
||||||
set_property(TARGET GLEW::glew_s
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew_s
|
|
||||||
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_STATIC_LIBRARY_DEBUG)
|
|
||||||
set_property(TARGET GLEW::glew_s
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::glew_s
|
|
||||||
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT TARGET GLEW::GLEW)
|
|
||||||
if(GLEW_VERBOSE)
|
|
||||||
message(STATUS "FindGLEW: Creating GLEW::GLEW imported target.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(GLEW::GLEW UNKNOWN IMPORTED)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
|
|
||||||
else()
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(TARGET GLEW::glew)
|
|
||||||
if(GLEW_SHARED_LIBRARY_RELEASE)
|
|
||||||
set_property(TARGET GLEW::GLEW
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_SHARED_LIBRARY_DEBUG)
|
|
||||||
set_property(TARGET GLEW::GLEW
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
elseif(TARGET GLEW::glew_s)
|
|
||||||
if(GLEW_STATIC_LIBRARY_RELEASE)
|
|
||||||
set_property(TARGET GLEW::GLEW
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}"
|
|
||||||
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GLEW_STATIC_LIBRARY_DEBUG AND GLEW_USE_STATIC_LIBS)
|
|
||||||
set_property(TARGET GLEW::GLEW
|
|
||||||
APPEND
|
|
||||||
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
|
||||||
|
|
||||||
set_target_properties(GLEW::GLEW
|
|
||||||
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}"
|
|
||||||
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
elseif(GLEW_VERBOSE)
|
|
||||||
message(WARNING "FindGLEW: no `GLEW::glew` or `GLEW::glew_s` target was created. Something went wrong in FindGLEW target creation.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
Vendored
-3
@@ -379,10 +379,8 @@ include(Boost/Boost.cmake)
|
|||||||
|
|
||||||
include(Cereal/Cereal.cmake)
|
include(Cereal/Cereal.cmake)
|
||||||
include(Qhull/Qhull.cmake)
|
include(Qhull/Qhull.cmake)
|
||||||
include(GLEW/GLEW.cmake)
|
|
||||||
|
|
||||||
include(GLFW/GLFW.cmake)
|
include(GLFW/GLFW.cmake)
|
||||||
include(OpenCSG/OpenCSG.cmake)
|
|
||||||
set(SLVS_PKG "")
|
set(SLVS_PKG "")
|
||||||
if (SLIC3R_CAD)
|
if (SLIC3R_CAD)
|
||||||
include(SLVS/SLVS.cmake)
|
include(SLVS/SLVS.cmake)
|
||||||
@@ -478,7 +476,6 @@ set(_dep_list
|
|||||||
dep_Draco
|
dep_Draco
|
||||||
dep_NLopt
|
dep_NLopt
|
||||||
dep_OpenVDB
|
dep_OpenVDB
|
||||||
dep_OpenCSG
|
|
||||||
${SLVS_PKG}
|
${SLVS_PKG}
|
||||||
dep_OpenCV
|
dep_OpenCV
|
||||||
dep_Eigen
|
dep_Eigen
|
||||||
|
|||||||
Vendored
-2
@@ -1,6 +1,4 @@
|
|||||||
orcaslicer_add_cmake_project(EXPAT
|
orcaslicer_add_cmake_project(EXPAT
|
||||||
# GIT_REPOSITORY https://github.com/nigels-com/glew.git
|
|
||||||
# GIT_TAG 3a8eff7 # 2.1.0
|
|
||||||
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/expat
|
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/expat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Vendored
-14
@@ -1,14 +0,0 @@
|
|||||||
# We have to check for OpenGL to compile GLEW
|
|
||||||
set(OpenGL_GL_PREFERENCE "LEGACY") # to prevent a nasty warning by cmake
|
|
||||||
find_package(OpenGL QUIET REQUIRED)
|
|
||||||
|
|
||||||
orcaslicer_add_cmake_project(
|
|
||||||
GLEW
|
|
||||||
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew
|
|
||||||
CMAKE_ARGS
|
|
||||||
-DGLEW_USE_EGL=OFF
|
|
||||||
)
|
|
||||||
|
|
||||||
if (MSVC)
|
|
||||||
add_debug_dep(dep_GLEW)
|
|
||||||
endif ()
|
|
||||||
Vendored
-44
@@ -1,44 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
|
||||||
project(GLEW)
|
|
||||||
|
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
|
|
||||||
# Allow parent project to control EGL usage.
|
|
||||||
# Default to OFF since OrcaSlicer forces GDK_BACKEND=x11 (using GLX contexts).
|
|
||||||
# GLEW must use glXGetProcAddressARB (GLX) to match wxWidgets GL canvas.
|
|
||||||
# Using EGL function loading with GLX contexts causes rendering failures.
|
|
||||||
option(GLEW_USE_EGL "Use EGL instead of GLX for OpenGL function loading" OFF)
|
|
||||||
|
|
||||||
if(GLEW_USE_EGL)
|
|
||||||
message(STATUS "Building GLEW with EGL support")
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL")
|
|
||||||
else()
|
|
||||||
message(STATUS "Building GLEW with GLX support")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(GLEW src/glew.c)
|
|
||||||
target_include_directories(GLEW PRIVATE include/)
|
|
||||||
target_link_libraries(GLEW PUBLIC OpenGL::GL)
|
|
||||||
|
|
||||||
if (NOT BUILD_SHARED_LIBS)
|
|
||||||
target_compile_definitions(GLEW PUBLIC GLEW_STATIC)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
|
|
||||||
install(
|
|
||||||
FILES
|
|
||||||
${PROJECT_SOURCE_DIR}/include/GL/glew.h
|
|
||||||
${PROJECT_SOURCE_DIR}/include/GL/wglew.h
|
|
||||||
${PROJECT_SOURCE_DIR}/include/GL/glxew.h
|
|
||||||
${PROJECT_SOURCE_DIR}/include/GL/eglew.h
|
|
||||||
DESTINATION
|
|
||||||
${CMAKE_INSTALL_INCLUDEDIR}/GL
|
|
||||||
)
|
|
||||||
|
|
||||||
install(TARGETS GLEW GLEW
|
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
||||||
)
|
|
||||||
Vendored
-73
@@ -1,73 +0,0 @@
|
|||||||
The OpenGL Extension Wrangler Library
|
|
||||||
Copyright (C) 2002-2007, Milan Ikits <milan ikits[]ieee org>
|
|
||||||
Copyright (C) 2002-2007, Marcelo E. Magallon <mmagallo[]debian org>
|
|
||||||
Copyright (C) 2002, Lev Povalahev
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* The name of the author may be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
||||||
THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
|
||||||
Mesa 3-D graphics library
|
|
||||||
Version: 7.0
|
|
||||||
|
|
||||||
Copyright (C) 1999-2007 Brian Paul All Rights Reserved.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
Copyright (c) 2007 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.
|
|
||||||
Vendored
-251
@@ -1,251 +0,0 @@
|
|||||||
# GLEW - The OpenGL Extension Wrangler Library
|
|
||||||
|
|
||||||
The OpenGL Extension Wrangler Library (GLEW) is a cross-platform open-source C/C++ extension loading library. GLEW provides efficient run-time mechanisms for determining which OpenGL extensions are supported on the target platform. OpenGL core and extension functionality is exposed in a single header file. GLEW has been tested on a variety of operating systems, including Windows, Linux, Mac OS X, FreeBSD, Irix, and Solaris.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
http://glew.sourceforge.net/
|
|
||||||
|
|
||||||
https://github.com/nigels-com/glew
|
|
||||||
|
|
||||||
[](https://travis-ci.org/nigels-com/glew)
|
|
||||||
[](https://gitter.im/nigels-com/glew?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
[](https://sourceforge.net/projects/glew/files/latest/download)
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
* [Downloads](#downloads)
|
|
||||||
* [Recent snapshots](#recent-snapshots)
|
|
||||||
* [Build](#build)
|
|
||||||
* [Linux and Mac](#linux-and-mac)
|
|
||||||
* [Using GNU Make](#using-gnu-make)
|
|
||||||
* [Install build tools](#install-build-tools)
|
|
||||||
* [Build](#build-1)
|
|
||||||
* [Linux EGL](#linux-egl)
|
|
||||||
* [Linux mingw-w64](#linux-mingw-w64)
|
|
||||||
* [Using cmake](#using-cmake)
|
|
||||||
* [Install build tools](#install-build-tools-1)
|
|
||||||
* [Build](#build-2)
|
|
||||||
* [Windows](#windows)
|
|
||||||
* [Visual Studio](#visual-studio)
|
|
||||||
* [MSYS/Mingw](#msysmingw)
|
|
||||||
* [MSYS2/Mingw-w64](#msys2mingw-w64)
|
|
||||||
* [glewinfo](#glewinfo)
|
|
||||||
* [Code Generation](#code-generation)
|
|
||||||
* [Authors](#authors)
|
|
||||||
* [Contributions](#contributions)
|
|
||||||
* [Copyright and Licensing](#copyright-and-licensing)
|
|
||||||
|
|
||||||
## Downloads
|
|
||||||
|
|
||||||
Current release is [2.1.0](https://sourceforge.net/projects/glew/files/glew/2.1.0/).
|
|
||||||
[(Change Log)](http://glew.sourceforge.net/log.html)
|
|
||||||
|
|
||||||
Sources available as
|
|
||||||
[ZIP](https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.zip/download) or
|
|
||||||
[TGZ](https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.tgz/download).
|
|
||||||
|
|
||||||
Windows binaries for [32-bit and 64-bit](https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0-win32.zip/download).
|
|
||||||
|
|
||||||
### Recent snapshots
|
|
||||||
|
|
||||||
Snapshots may contain new features, bug-fixes or new OpenGL extensions ahead of tested, official releases.
|
|
||||||
|
|
||||||
[glew-20200115.tgz](https://sourceforge.net/projects/glew/files/glew/snapshots/glew-20200115.tgz/download) *GLEW 2.2.0 RC3: fixes*
|
|
||||||
|
|
||||||
[glew-20190928.tgz](https://sourceforge.net/projects/glew/files/glew/snapshots/glew-20190928.tgz/download) *GLEW 2.2.0 RC2: New extensions, bug fixes*
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
It is highly recommended to build from a tgz or zip release snapshot.
|
|
||||||
The code generation workflow is a complex brew of gnu make, perl and python, that works best on Linux or Mac.
|
|
||||||
The code generation is known to work on Windows using [MSYS2](https://www.msys2.org/).
|
|
||||||
For most end-users of GLEW the official releases are the best choice, with first class support.
|
|
||||||
|
|
||||||
### Linux and Mac
|
|
||||||
|
|
||||||
#### Using GNU Make
|
|
||||||
|
|
||||||
GNU make is the primary build system for GLEW, historically.
|
|
||||||
It includes targets for building the sources and headers, for maintenance purposes.
|
|
||||||
|
|
||||||
##### Install build tools
|
|
||||||
|
|
||||||
Debian/Ubuntu/Mint: `$ sudo apt-get install build-essential libxmu-dev libxi-dev libgl-dev`
|
|
||||||
|
|
||||||
RedHat/CentOS/Fedora: `$ sudo yum install libXmu-devel libXi-devel libGL-devel`
|
|
||||||
|
|
||||||
FreeBSD: `# pkg install xorg lang/gcc git cmake gmake bash python perl5`
|
|
||||||
|
|
||||||
##### Build
|
|
||||||
|
|
||||||
$ make
|
|
||||||
$ sudo make install
|
|
||||||
$ make clean
|
|
||||||
|
|
||||||
Targets: `all, glew.lib (sub-targets: glew.lib.shared, glew.lib.static), glew.bin, clean, install, uninstall`
|
|
||||||
|
|
||||||
Variables: `SYSTEM=linux-clang, GLEW_DEST=/usr/local, STRIP=`
|
|
||||||
|
|
||||||
_Note: you may need to call `make` in the **auto** folder first_
|
|
||||||
|
|
||||||
##### Linux EGL
|
|
||||||
|
|
||||||
$ sudo apt install libegl1-mesa-dev
|
|
||||||
$ make SYSTEM=linux-egl
|
|
||||||
|
|
||||||
##### Linux mingw-w64
|
|
||||||
|
|
||||||
$ sudo apt install mingw-w64
|
|
||||||
$ make SYSTEM=linux-mingw32
|
|
||||||
$ make SYSTEM=linux-mingw64
|
|
||||||
|
|
||||||
#### Using cmake
|
|
||||||
|
|
||||||
The cmake build is mostly contributer maintained.
|
|
||||||
Due to the multitude of use cases this is maintained on a _best effort_ basis.
|
|
||||||
Pull requests are welcome.
|
|
||||||
|
|
||||||
*CMake 2.8.12 or higher is required.*
|
|
||||||
|
|
||||||
##### Install build tools
|
|
||||||
|
|
||||||
Debian/Ubuntu/Mint: `$ sudo apt-get install build-essential libxmu-dev libxi-dev libgl-dev cmake git`
|
|
||||||
|
|
||||||
RedHat/CentOS/Fedora: `$ sudo yum install libXmu-devel libXi-devel libGL-devel cmake git`
|
|
||||||
|
|
||||||
##### Build
|
|
||||||
|
|
||||||
$ cd build
|
|
||||||
$ cmake ./cmake
|
|
||||||
$ make -j4
|
|
||||||
|
|
||||||
| Target | Description |
|
|
||||||
| ---------- | ----------- |
|
|
||||||
| glew | Build the glew shared library. |
|
|
||||||
| glew_s | Build the glew static library. |
|
|
||||||
| glewinfo | Build the `glewinfo` executable (requires `BUILD_UTILS` to be `ON`). |
|
|
||||||
| visualinfo | Build the `visualinfo` executable (requires `BUILD_UTILS` to be `ON`). |
|
|
||||||
| install | Install all enabled targets into `CMAKE_INSTALL_PREFIX`. |
|
|
||||||
| clean | Clean up build artifacts. |
|
|
||||||
| all | Build all enabled targets (default target). |
|
|
||||||
|
|
||||||
| Variables | Description |
|
|
||||||
| --------------- | ----------- |
|
|
||||||
| BUILD_UTILS | Build the `glewinfo` and `visualinfo` executables. |
|
|
||||||
| GLEW_REGAL | Build in Regal mode. |
|
|
||||||
| BUILD_FRAMEWORK | Build as MacOSX Framework. Setting `CMAKE_INSTALL_PREFIX` to `/Library/Frameworks` is recommended. |
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
#### Visual Studio
|
|
||||||
|
|
||||||
Use the provided Visual Studio project file in build/vc15/
|
|
||||||
|
|
||||||
Projects for vc6, vc10, vc12 and vc14 are also provided
|
|
||||||
|
|
||||||
#### MSYS/Mingw
|
|
||||||
|
|
||||||
Available from [Mingw](http://www.mingw.org/)
|
|
||||||
|
|
||||||
Requirements: bash, make, gcc
|
|
||||||
|
|
||||||
$ mingw32-make
|
|
||||||
$ mingw32-make install
|
|
||||||
$ mingw32-make install.all
|
|
||||||
|
|
||||||
Alternative toolchain: `SYSTEM=mingw-win32`
|
|
||||||
|
|
||||||
#### MSYS2/Mingw-w64
|
|
||||||
|
|
||||||
Available from [Msys2](http://msys2.github.io/) and/or [Mingw-w64](http://mingw-w64.org/)
|
|
||||||
|
|
||||||
Requirements: bash, make, gcc
|
|
||||||
|
|
||||||
$ pacman -S gcc make mingw-w64-i686-gcc mingw-w64-x86_64-gcc
|
|
||||||
$ make
|
|
||||||
$ make install
|
|
||||||
$ make install.all
|
|
||||||
|
|
||||||
Alternative toolchain: `SYSTEM=msys, SYSTEM=msys-win32, SYSTEM=msys-win64`
|
|
||||||
|
|
||||||
## glewinfo
|
|
||||||
|
|
||||||
`glewinfo` is a command-line tool useful for inspecting the capabilities of an
|
|
||||||
OpenGL implementation and GLEW support for that. Please include `glewinfo.txt`
|
|
||||||
with bug reports, as appropriate.
|
|
||||||
|
|
||||||
---------------------------
|
|
||||||
GLEW Extension Info
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
GLEW version 2.0.0
|
|
||||||
Reporting capabilities of pixelformat 3
|
|
||||||
Running on a Intel(R) HD Graphics 3000 from Intel
|
|
||||||
OpenGL version 3.1.0 - Build 9.17.10.4229 is supported
|
|
||||||
|
|
||||||
GL_VERSION_1_1: OK
|
|
||||||
---------------
|
|
||||||
|
|
||||||
GL_VERSION_1_2: OK
|
|
||||||
---------------
|
|
||||||
glCopyTexSubImage3D: OK
|
|
||||||
glDrawRangeElements: OK
|
|
||||||
glTexImage3D: OK
|
|
||||||
glTexSubImage3D: OK
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
## Code Generation
|
|
||||||
|
|
||||||
A Unix or Mac environment is needed for building GLEW from scratch to
|
|
||||||
include new extensions, or customize the code generation. The extension
|
|
||||||
data is regenerated from the top level source directory with:
|
|
||||||
|
|
||||||
make extensions
|
|
||||||
|
|
||||||
An alternative to generating the GLEW sources from scratch is to
|
|
||||||
download a pre-generated (unsupported) snapshot:
|
|
||||||
|
|
||||||
https://sourceforge.net/projects/glew/files/glew/snapshots/
|
|
||||||
|
|
||||||
## Authors
|
|
||||||
|
|
||||||
GLEW is currently maintained by [Nigel Stewart](https://github.com/nigels-com)
|
|
||||||
with bug fixes, new OpenGL extension support and new releases.
|
|
||||||
|
|
||||||
GLEW was developed by [Milan Ikits](http://www.cs.utah.edu/~ikits/)
|
|
||||||
and [Marcelo Magallon](http://wwwvis.informatik.uni-stuttgart.de/~magallon/).
|
|
||||||
Aaron Lefohn, Joe Kniss, and Chris Wyman were the first users and also
|
|
||||||
assisted with the design and debugging process.
|
|
||||||
|
|
||||||
The acronym GLEW originates from Aaron Lefohn.
|
|
||||||
Pasi Kärkkäinen identified and fixed several problems with
|
|
||||||
GLX and SDL. Nate Robins created the `wglinfo` utility, to
|
|
||||||
which modifications were made by Michael Wimmer.
|
|
||||||
|
|
||||||
## Contributions
|
|
||||||
|
|
||||||
GLEW welcomes community contributions. Typically these are co-ordinated
|
|
||||||
via [Issues](https://github.com/nigels-com/glew/issues) or
|
|
||||||
[Pull Requests](https://github.com/nigels-com/glew/pulls) in the
|
|
||||||
GitHub web interface.
|
|
||||||
|
|
||||||
Be sure to mention platform and compiler toolchain details when filing
|
|
||||||
a bug report. The output of `glewinfo` can be quite useful for discussion
|
|
||||||
also.
|
|
||||||
|
|
||||||
Generally GLEW is usually released once a year, around the time of the Siggraph
|
|
||||||
computer graphics conference. If you're not using the current release
|
|
||||||
version of GLEW, be sure to check if the issue or bug is fixed there.
|
|
||||||
|
|
||||||
## Copyright and Licensing
|
|
||||||
|
|
||||||
GLEW is originally derived from the EXTGL project by Lev Povalahev.
|
|
||||||
The source code is licensed under the
|
|
||||||
[Modified BSD License](http://glew.sourceforge.net/glew.txt), the
|
|
||||||
[Mesa 3-D License](http://glew.sourceforge.net/mesa.txt) (MIT) and the
|
|
||||||
[Khronos License](http://glew.sourceforge.net/khronos.txt) (MIT).
|
|
||||||
|
|
||||||
The automatic code generation scripts are released under the
|
|
||||||
[GNU GPL](http://glew.sourceforge.net/gpl.txt).
|
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
2.2.0
|
|
||||||
Vendored
-3051
File diff suppressed because it is too large
Load Diff
Vendored
-26427
File diff suppressed because it is too large
Load Diff
Vendored
-1831
File diff suppressed because it is too large
Load Diff
Vendored
-1468
File diff suppressed because it is too large
Load Diff
Vendored
-31949
File diff suppressed because it is too large
Load Diff
Vendored
-101
@@ -1,101 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
|
||||||
|
|
||||||
project(OpenCSG)
|
|
||||||
|
|
||||||
if (NOT BUILD_SHARED_LIBS)
|
|
||||||
set(GLEW_USE_STATIC_LIBS ON)
|
|
||||||
elseif (MSVC)
|
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
|
|
||||||
set(GLEW_VERBOSE ON)
|
|
||||||
find_package(GLEW 1.13.0 REQUIRED)
|
|
||||||
|
|
||||||
set(_srcfiles
|
|
||||||
src/area.cpp
|
|
||||||
src/batch.cpp
|
|
||||||
src/context.cpp
|
|
||||||
src/channelManager.cpp
|
|
||||||
src/frameBufferObject.cpp
|
|
||||||
src/frameBufferObjectExt.cpp
|
|
||||||
src/occlusionQuery.cpp
|
|
||||||
src/opencsgRender.cpp
|
|
||||||
src/openglHelper.cpp
|
|
||||||
src/pBufferTexture.cpp
|
|
||||||
src/primitive.cpp
|
|
||||||
src/primitiveHelper.cpp
|
|
||||||
src/renderGoldfeather.cpp
|
|
||||||
src/renderSCS.cpp
|
|
||||||
src/scissorMemo.cpp
|
|
||||||
src/settings.cpp
|
|
||||||
src/stencilManager.cpp
|
|
||||||
RenderTexture/RenderTexture.cpp
|
|
||||||
include/opencsg.h
|
|
||||||
src/opencsgConfig.h
|
|
||||||
src/area.h
|
|
||||||
src/batch.h
|
|
||||||
src/context.h
|
|
||||||
src/channelManager.h
|
|
||||||
src/frameBufferObject.h
|
|
||||||
src/frameBufferObjectExt.h
|
|
||||||
src/occlusionQuery.h
|
|
||||||
src/offscreenBuffer.h
|
|
||||||
src/opencsgRender.h
|
|
||||||
src/openglHelper.h
|
|
||||||
src/pBufferTexture.h
|
|
||||||
src/primitiveHelper.h
|
|
||||||
src/scissorMemo.h
|
|
||||||
src/settings.h
|
|
||||||
src/stencilManager.h
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(opencsg ${_srcfiles})
|
|
||||||
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
|
||||||
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>)
|
|
||||||
target_link_libraries(opencsg PRIVATE GLEW::GLEW OpenGL::GL)
|
|
||||||
|
|
||||||
include(CMakePackageConfigHelpers)
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
|
|
||||||
write_basic_package_version_file(
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
|
||||||
VERSION 1.4.2
|
|
||||||
COMPATIBILITY AnyNewerVersion
|
|
||||||
)
|
|
||||||
|
|
||||||
install(TARGETS opencsg
|
|
||||||
EXPORT ${PROJECT_NAME}Targets
|
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
|
||||||
|
|
||||||
export(EXPORT ${PROJECT_NAME}Targets
|
|
||||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
|
||||||
NAMESPACE ${PROJECT_NAME}:: )
|
|
||||||
|
|
||||||
set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
|
||||||
|
|
||||||
install(EXPORT ${PROJECT_NAME}Targets
|
|
||||||
FILE
|
|
||||||
"${PROJECT_NAME}Config.cmake"
|
|
||||||
NAMESPACE
|
|
||||||
${PROJECT_NAME}::
|
|
||||||
DESTINATION
|
|
||||||
${ConfigPackageLocation}
|
|
||||||
)
|
|
||||||
install(
|
|
||||||
FILES
|
|
||||||
${PROJECT_SOURCE_DIR}/include/opencsg.h
|
|
||||||
DESTINATION
|
|
||||||
${CMAKE_INSTALL_INCLUDEDIR}/opencsg
|
|
||||||
)
|
|
||||||
install(
|
|
||||||
FILES
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
|
||||||
DESTINATION
|
|
||||||
${ConfigPackageLocation}
|
|
||||||
)
|
|
||||||
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
orcaslicer_add_cmake_project(OpenCSG
|
|
||||||
# GIT_REPOSITORY https://github.com/floriankirsch/OpenCSG.git
|
|
||||||
# GIT_TAG 83e274457b46c9ad11a4ee599203250b1618f3b9 #v1.4.2
|
|
||||||
URL https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
|
|
||||||
URL_HASH SHA256=51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
|
|
||||||
PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in ./CMakeLists.txt
|
|
||||||
DEPENDS dep_GLEW
|
|
||||||
)
|
|
||||||
|
|
||||||
if (TARGET ${ZLIB_PKG})
|
|
||||||
add_dependencies(dep_OpenCSG ${ZLIB_PKG})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (MSVC)
|
|
||||||
add_debug_dep(dep_OpenCSG)
|
|
||||||
endif ()
|
|
||||||
Vendored
+29
@@ -44,6 +44,18 @@ else()
|
|||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
|
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
|
||||||
else()
|
else()
|
||||||
|
# A static library that is embedded into a shared object must not export
|
||||||
|
# its symbols. On Linux the running process also loads the system OpenSSL
|
||||||
|
# 3.x (WebKitGTK/gnutls pull in libcrypto.so.3), and CPython's _ssl and
|
||||||
|
# _hashlib are dlopened (RTLD_LOCAL) DSOs that each embed this OpenSSL.
|
||||||
|
# With default visibility their unversioned OpenSSL references are
|
||||||
|
# preempted by that global 3.x copy, mixing the 1.1.1 and 3.x ABIs and
|
||||||
|
# corrupting the heap (ssl.create_default_context() aborts). Hidden
|
||||||
|
# visibility makes each embedded copy self-contained. Linux-only: macOS
|
||||||
|
# binds dylibs with a two-level namespace (no interposition) and ships no
|
||||||
|
# OpenSSL, and Windows has no equivalent flag and no system OpenSSL to
|
||||||
|
# collide with.
|
||||||
|
set(_openssl_extra_cflags -fvisibility=hidden)
|
||||||
set(_conf_cmd env "CC=${CMAKE_C_COMPILER}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" "./config")
|
set(_conf_cmd env "CC=${CMAKE_C_COMPILER}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" "./config")
|
||||||
endif()
|
endif()
|
||||||
set(_cross_comp_prefix_line "")
|
set(_cross_comp_prefix_line "")
|
||||||
@@ -102,3 +114,20 @@ ExternalProject_Add_Step(dep_OpenSSL install_cmake_files
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory openssl "${DESTDIR}${CMAKE_INSTALL_LIBDIR}/cmake/openssl"
|
COMMAND ${CMAKE_COMMAND} -E copy_directory openssl "${DESTDIR}${CMAKE_INSTALL_LIBDIR}/cmake/openssl"
|
||||||
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (NOT WIN32 AND NOT APPLE)
|
||||||
|
# OpenSSL's object rules do not depend on CFLAGS, so reconfiguring it (for
|
||||||
|
# example to add -fvisibility=hidden) relinks the archives from stale
|
||||||
|
# objects instead of recompiling them, and the change silently has no
|
||||||
|
# effect. Drop the objects whenever this recipe changes so the next build
|
||||||
|
# actually recompiles them.
|
||||||
|
ExternalProject_Get_Property(dep_OpenSSL SOURCE_DIR)
|
||||||
|
ExternalProject_Add_Step(dep_OpenSSL clean_objects
|
||||||
|
DEPENDEES configure
|
||||||
|
DEPENDERS build
|
||||||
|
COMMAND make clean
|
||||||
|
WORKING_DIRECTORY "${SOURCE_DIR}"
|
||||||
|
DEPENDS "${CMAKE_CURRENT_LIST_FILE}"
|
||||||
|
COMMENT "OpenSSL: cleaning objects after a recipe change"
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|||||||
Vendored
+24
@@ -299,3 +299,27 @@ endif()
|
|||||||
if(TARGET dep_ZLIB)
|
if(TARGET dep_ZLIB)
|
||||||
add_dependencies(dep_python3 dep_ZLIB)
|
add_dependencies(dep_python3 dep_ZLIB)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (NOT WIN32 AND NOT APPLE)
|
||||||
|
# CPython's Makefile rules for _ssl and _hashlib depend only on their own
|
||||||
|
# sources, not on the OpenSSL archives, so a rebuilt OpenSSL does not make
|
||||||
|
# them relink and they keep the previous symbols. On an incremental tree,
|
||||||
|
# drop the built modules and relink them against the current OpenSSL; a
|
||||||
|
# fresh build is left alone (its PGO target builds them). "make" alone is a
|
||||||
|
# no-op once PGO has run, so sharedmods is invoked explicitly.
|
||||||
|
ExternalProject_Get_Property(dep_python3 SOURCE_DIR)
|
||||||
|
file(GLOB _python_ssl_modules
|
||||||
|
"${SOURCE_DIR}/Modules/_ssl*.so"
|
||||||
|
"${SOURCE_DIR}/Modules/_hashlib*.so")
|
||||||
|
if (_python_ssl_modules)
|
||||||
|
ExternalProject_Add_Step(dep_python3 relink_ssl_extensions
|
||||||
|
DEPENDEES configure
|
||||||
|
DEPENDERS build
|
||||||
|
COMMAND sh -c "rm -f '${SOURCE_DIR}'/Modules/_ssl*.so '${SOURCE_DIR}'/Modules/_hashlib*.so && make -j${NPROC} sharedmods"
|
||||||
|
WORKING_DIRECTORY "${SOURCE_DIR}"
|
||||||
|
COMMENT "CPython: relinking _ssl/_hashlib against the current OpenSSL"
|
||||||
|
DEPENDS "${CMAKE_CURRENT_LIST_FILE}"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/../OpenSSL/OpenSSL.cmake"
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ public:
|
|||||||
tail->next = std::move(p);
|
tail->next = std::move(p);
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
}
|
}
|
||||||
data_cond.notify_one();
|
disrupt_wait_for_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
void wait_and_pop(T& value)
|
void wait_and_pop(T& value)
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
# Adaptive TPMS infill — High Level Design
|
||||||
|
|
||||||
|
## Purpose and scope
|
||||||
|
|
||||||
|
`tpms_adaptive` grades the sparse infill of the Gyroid, TPMS-D and TPMS-FK
|
||||||
|
patterns inside the object: the cells grow continuously from the surface
|
||||||
|
towards the center of the object. `distance_warp`, `smooth_blend` and
|
||||||
|
`stepped_shells` follow the distance to the nearest surface, including the top
|
||||||
|
and bottom, like concentric shells; `lobes` follows the whole 3D shape towards
|
||||||
|
the center of each lobe of the object; `normal_z`, `normal_y` and `normal_x`
|
||||||
|
follow each section of the object normal to that axis, so the grading does not
|
||||||
|
change along the axis, as suits a profile extruded along it.
|
||||||
|
`sparse_infill_density` is the density at the surface, `tpms_interior_density`
|
||||||
|
the density at the center, and `tpms_adaptive_gradient` picks how the density
|
||||||
|
goes from one to the other. Only internal sparse infill is graded; the Gyroid
|
||||||
|
Z-buckling optimization does not apply to it.
|
||||||
|
|
||||||
|
The design has two parts: a field built once per object, and a pattern made
|
||||||
|
from it, warped around the center of each lobe of a body so that its cell size
|
||||||
|
follows the field, or, in the modes following the distance to the surface,
|
||||||
|
split into shells or blended between densities.
|
||||||
|
|
||||||
|
## Radial field
|
||||||
|
|
||||||
|
`TpmsRadialField` gives every point of an object the center of its lobe and a
|
||||||
|
radial coordinate: 0 at the center, 1 at the surface along the ray from the
|
||||||
|
center. `PrintObject::prepare_tpms_radial_fields()` builds it in
|
||||||
|
`bridge_over_infill()`, next to the adaptive cubic octree, because the anchoring
|
||||||
|
infill generated there has to match the printed infill. A field is built for
|
||||||
|
every mode a region uses, and is shared by the regions using that mode: the
|
||||||
|
field depends on the geometry only, the densities are applied per region in the
|
||||||
|
fill. An object thinner than the grid cells has no body in the field; no field
|
||||||
|
is kept then, and the infill falls back to the regular pattern. In the modes
|
||||||
|
following the distance to the surface, the field also gives the depth of every
|
||||||
|
point (see below).
|
||||||
|
|
||||||
|
A regular 3D grid of cubic cells is rasterized from the `lslices` of the layers,
|
||||||
|
so the field follows what is printed: negative volumes, the union of
|
||||||
|
overlapping parts and holes are taken into account, and the mesh does not need
|
||||||
|
to be closed. A padding node around the grid is always outside. The grid is
|
||||||
|
capped at about a million nodes, with cells no smaller than 0.5 mm.
|
||||||
|
|
||||||
|
- Bodies are the connected inside nodes. Each is graded on its own, so separate
|
||||||
|
parts of one object each get their own sparse center.
|
||||||
|
- A body is split into lobes around the local maxima of the depth, by an exact
|
||||||
|
Euclidean distance transform (Felzenszwalb and Huttenlocher, one pass per
|
||||||
|
axis). Two maxima are in separate lobes when the depth along the segment
|
||||||
|
between them drops below 0.8 of the shallower one, like at the neck between
|
||||||
|
two united spheres; maxima shallower than 0.3 of the deepest one are ignored.
|
||||||
|
A maximum joins the first lobe whose first maximum it sees without a neck.
|
||||||
|
The lobes are made one at a time, the remaining maxima tested against the
|
||||||
|
first one in parallel, as a plate has a whole plane of them.
|
||||||
|
Where the depth ties along a line or a plane, as in a tall box, the lobe's
|
||||||
|
center is the node nearest to the middle of the tied nodes, so the center is
|
||||||
|
in the middle of the height and not a column.
|
||||||
|
- A point belongs to the lobe it is nearest to relative to their depths, so the
|
||||||
|
side between two lobes is nearer to the smaller one. Near that side, within a
|
||||||
|
tenth of that relative distance, the patterns of the lobes morph into each
|
||||||
|
other, so the lines stay continuous. Every lobe in that range takes part, up
|
||||||
|
to four, so the morph is also continuous where three or four lobes meet.
|
||||||
|
- The reach of a lobe is the distance from its center to the first exit along
|
||||||
|
24 x 48 latitude-longitude directions, smoothed twice over neighbouring
|
||||||
|
directions in log space. Towards a neighbouring lobe it stops at twice the
|
||||||
|
distance to the side between them, so that side is graded half way, as deep
|
||||||
|
as a neck is, rather than as sparse as the center or as dense as the surface.
|
||||||
|
Only the lobes whose centers are near enough to be nearer at the current
|
||||||
|
distance are compared along a ray, so many lobes, as in a perforated plate,
|
||||||
|
stay cheap.
|
||||||
|
The radial coordinate of a point is its distance to the center over the reach
|
||||||
|
in its direction. Behind a gap, as across the hole
|
||||||
|
of a ring, the radial coordinate is above 1 and the infill keeps the surface
|
||||||
|
density.
|
||||||
|
- Every outside node belongs to its nearest body, so points near a surface find
|
||||||
|
their body without a search. With a single body, all nodes belong to it.
|
||||||
|
|
||||||
|
In the 2D modes, every plane of nodes normal to the axis is a field of its own:
|
||||||
|
the distance transform skips the axis, bodies, lobes and the nearest body are
|
||||||
|
found within the plane, and the reach is sampled on a circle of 48 directions.
|
||||||
|
A point is looked up in the two planes around it, the weights of their lobes
|
||||||
|
interpolated along the axis, so the grading does not step between planes; a
|
||||||
|
plane without a body uses the nearest one that has one. The planes are a cell
|
||||||
|
apart, not a layer: where the sections change abruptly, as at a step, the
|
||||||
|
patterns of the two planes morph into each other over that cell.
|
||||||
|
|
||||||
|
A distance to the nearest surface would be the obvious field, but no smooth map
|
||||||
|
follows it. By the divergence theorem, the mean scale of a map over a body is
|
||||||
|
fixed by its values on the surface: a map that keeps the full density along the
|
||||||
|
whole surface, as the distance would ask under the top and bottom, has the mean
|
||||||
|
density of the uniform infill, the sparser core being paid for by lines crowding
|
||||||
|
along the walls. The layers of a plate at different depths would also need
|
||||||
|
different line spacings in the same directions, which no continuous map allows
|
||||||
|
without shearing across the plate. Following the distance needs changes of the
|
||||||
|
topology of the lattice (see below). The radial coordinate instead grades what a
|
||||||
|
single map can: towards one point.
|
||||||
|
|
||||||
|
## Warped pattern
|
||||||
|
|
||||||
|
The pattern is evaluated on warped coordinates:
|
||||||
|
|
||||||
|
TPMS(f_surface * m(t) * (p - center))
|
||||||
|
|
||||||
|
where `m` scales the pattern around the center of the lobe: its frequency is
|
||||||
|
`m + t * m'` along the ray and `m` across it. `m(t)` is the mean of the target
|
||||||
|
scale over the ball of radius `t`, `3 / t^3 * integral of s^2 * target(s) ds`, so
|
||||||
|
the mean of the three, and with it the density, follows the gradient. The cells
|
||||||
|
are round at the center; near the surface they are flattened, with the lines
|
||||||
|
running parallel to it. Beyond the surface the target is the surface scale, so
|
||||||
|
the warp extends continuously outside.
|
||||||
|
|
||||||
|
In the 2D modes only the coordinates within the plane are warped, and `m(t)` is
|
||||||
|
the mean over the disc, `2 / t^2 * integral of s * target(s) ds`. Along the axis
|
||||||
|
the pattern keeps the interior frequency: scaling it with `m` would shear the
|
||||||
|
pattern by the distance along the axis times the gradient of `m`, without bound
|
||||||
|
on a long object. The cells are round at the center and stretched along the
|
||||||
|
axis near the surface. With Normal Z the layers are graded exactly, since
|
||||||
|
the lines of a layer follow its in-plane frequencies; normal to X or Y, the
|
||||||
|
layers near the sides are as dense as the larger of the two frequencies in the
|
||||||
|
layer, which is the surface one.
|
||||||
|
|
||||||
|
Evaluating a TPMS at a frequency that varies with the position without such a
|
||||||
|
map distorts it wherever the frequency changes, because the phase also changes
|
||||||
|
with the gradient of the frequency times the distance from the origin. Fitting a
|
||||||
|
smooth map to a varying isotropic scale in the least-squares sense (a Poisson
|
||||||
|
problem per axis) cannot grade strongly: its divergence is the target scale plus
|
||||||
|
a harmonic function pinned by the surface, which keeps the scale in the core
|
||||||
|
near two thirds of the surface one. Following a distance exactly needs the
|
||||||
|
lattice to change its topology, by blending lattices of different densities or
|
||||||
|
filling shells of equal distance with them, as the modes following the distance
|
||||||
|
to the surface do.
|
||||||
|
|
||||||
|
The target scale at depth `d = 1 - t`, with `S` the surface and `I` the interior
|
||||||
|
frequency, both from each pattern's own density calibration:
|
||||||
|
|
||||||
|
| Gradient | Scale |
|
||||||
|
|-------------|------------------------|
|
||||||
|
| Linear | `1 + (I / S - 1) * d` |
|
||||||
|
| Quadratic | `1 + (I / S - 1) * d^2`|
|
||||||
|
| Exponential | `(I / S)^d` |
|
||||||
|
|
||||||
|
With a denser surface, quadratic keeps the surface density deepest and
|
||||||
|
exponential drops fastest. A denser interior works the same way.
|
||||||
|
|
||||||
|
The zero level is extracted with marching squares like the regular TPMS-FK, on
|
||||||
|
a sampling grid fixed in the fill frame like the optimized Gyroid, so that every
|
||||||
|
region of a layer connects its lines the same way at the saddles of the pattern.
|
||||||
|
Loops narrower than two lines (shorter than `2 * PI * spacing`) are dropped, as
|
||||||
|
they would print as blobs. The fill works in a frame rotated by the infill
|
||||||
|
angle, so the radial field is looked up at the point rotated back into the
|
||||||
|
object frame, and the center rotated into the fill frame. Both use the middle of
|
||||||
|
the layer.
|
||||||
|
|
||||||
|
## Modes following the distance to the surface
|
||||||
|
|
||||||
|
`distance_warp`, `smooth_blend` and `stepped_shells` grade by the distance to
|
||||||
|
the nearest surface, including the top and bottom, as concentric shells do. The
|
||||||
|
depth of a point is that distance over the distance of the deepest point of its
|
||||||
|
body, from 0 at the surface to 1; the field keeps it for every node and
|
||||||
|
interpolates it between them. A tall box so keeps its whole axis as sparse as
|
||||||
|
its center, and a plate is graded through its thickness.
|
||||||
|
|
||||||
|
No single smooth pattern follows that depth without distortion (see above), so
|
||||||
|
the three modes trade differently:
|
||||||
|
|
||||||
|
- Distance warp keeps the lobes and the warp of Lobes, but its radial profile
|
||||||
|
comes from the depth. For every direction of a lobe, the mean depth over the
|
||||||
|
ball along the ray is sampled at 33 radii up to the reach, then smoothed over
|
||||||
|
the neighbouring directions like the reach. The radial coordinate is the one
|
||||||
|
of the linear profile with the same mean depth, `t = 4/3 * (1 - mean depth)`,
|
||||||
|
so with a linear gradient the mean cell size follows the depth exactly, and
|
||||||
|
with the others approximately. In a sphere or a cube, where the depth falls
|
||||||
|
linearly along every ray, it is Lobes. Elsewhere the profile changes with the
|
||||||
|
direction, and the warp shears where neighbouring directions differ, as in
|
||||||
|
plates and long bodies; right under the top of a long body the cells are
|
||||||
|
sparser within the layer, the warp moving their density into the height.
|
||||||
|
The profiles are smoothed over the directions like the reach, as sharper
|
||||||
|
ones shear the pattern across the layer, which adds lines. A long body is so
|
||||||
|
graded partly along its length, between Lobes and the distance.
|
||||||
|
- Smooth blend evaluates the regular patterns of the two levels around the
|
||||||
|
target of every point and blends them by a smoothstep over the whole gap
|
||||||
|
between the levels, here at most 2.5 times apart. The density follows the
|
||||||
|
depth without steps, but where two lattices blend, part of their lines run
|
||||||
|
along the blend, so fewer levels print fewer extra lines. From 25% to 5%,
|
||||||
|
three levels print about 0.45 of the uniform infill in a deep core whose
|
||||||
|
levels alone would print 0.3; levels 1.5 times apart print 0.6 to 0.7, and a
|
||||||
|
single blend from the surface to the interior 0.6.
|
||||||
|
- Stepped shells split each region of a layer into shells and fill every shell
|
||||||
|
with the regular pattern at its density. The densities are levels from the
|
||||||
|
surface to the interior density at most 1.5 times apart, five from 25% to 5%,
|
||||||
|
and a point takes the level nearest to its target on that geometric scale.
|
||||||
|
The shells are traced by marching squares of the continuous level over the
|
||||||
|
layer on a 0.5 mm grid fixed in the object, so every region of a layer gets
|
||||||
|
the same shells, and clipped to the region. Each shell is shrunk by half a
|
||||||
|
line, like a filled region, and its regular filler connects its lines along
|
||||||
|
that boundary, so the connections of two neighbouring shells lie side by side
|
||||||
|
instead of on top of each other. The pattern is never
|
||||||
|
distorted, but its lines end at every shell, and thin parts get thin shells.
|
||||||
|
The connections add lines: in the core of a 60 mm cube, about a third more
|
||||||
|
than the target.
|
||||||
|
|
||||||
|
Stepped shells and Smooth blend need neither lobes nor reaches, which are not
|
||||||
|
built for them.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- With `tpms_adaptive` disabled, or for other patterns, the fill parameters are reset
|
||||||
|
to their defaults, so they neither change the infill nor split fill batches.
|
||||||
|
- `Layer::get_sparse_infill_max_void_area()` uses the sparser of the two
|
||||||
|
densities, as the voids at the center are that large.
|
||||||
|
- At a sparse infill density of 100% the sparse infill is turned into solid
|
||||||
|
infill, so there is nothing to grade: the options are hidden and no field is
|
||||||
|
built.
|
||||||
|
- The adaptive options invalidate `posPrepareInfill`, which rebuilds the field
|
||||||
|
and the anchoring infill.
|
||||||
|
- An elongated body without a neck has one center, so its far ends are graded
|
||||||
|
as the outer part of the body, and the warp shears where the reach changes
|
||||||
|
quickly with the direction. A concave body, like an L, may be split into lobes
|
||||||
|
where its maxima cannot see each other in a straight line.
|
||||||
|
- Across a ray, the scale is the mean of the gradient from the center, so the
|
||||||
|
layers right under the top and above the bottom are sparser than the surface
|
||||||
|
density in their middle, and a plate is graded from its middle outwards rather
|
||||||
|
than through its thickness.
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# Center of mass markers — High Level Design
|
||||||
|
|
||||||
|
## Purpose and scope
|
||||||
|
|
||||||
|
The "Center of mass" item of the canvas toolbar menu, in the bottom left corner of the 3D view, marks
|
||||||
|
where the mass of the plate, of each object instance and of each body of an assembly is centered,
|
||||||
|
in Prepare and in Preview. It helps judge how parts will rest on the plate, for instance whether a
|
||||||
|
tall or leaning part could tip. It is a view setting: it changes nothing in the model, the slice or
|
||||||
|
the project file, and it does not reach plate thumbnails. The choice is kept in the app config as
|
||||||
|
`show_center_of_mass`, off by default. The assembly view and the Design tab have no markers.
|
||||||
|
|
||||||
|
Three kinds of marker share one shape, a sphere whose octants alternate between two colors:
|
||||||
|
|
||||||
|
- each plate, black and white, for everything on it;
|
||||||
|
- each object instance, light blue and white;
|
||||||
|
- each body of an assembly, red and yellow;
|
||||||
|
- in Preview, the supports and raft of each object instance, green and black.
|
||||||
|
|
||||||
|
A click on a marker opens a box beside it with the weight and volume of what it stands for, where its
|
||||||
|
center lies in that thing's bounding box and the size of the box, and its moments of inertia about
|
||||||
|
axes through the center parallel to x, y and z.
|
||||||
|
|
||||||
|
An assembly is an object of several parts or with negative volumes. Its bodies are the connected
|
||||||
|
solids its parts make once united, the bodies the separated infills option centers its infill on
|
||||||
|
(see separated-infills.md): parts that overlap or touch are one body, parts apart are separate
|
||||||
|
bodies. An object of one body, and every object of a single part, has no body markers, as its object
|
||||||
|
marker says it all.
|
||||||
|
|
||||||
|
Mass is volume times density, the `filament_density` of the filament that prints it, or 1.245 g/cm³
|
||||||
|
(`DEFAULT_FILAMENT_DENSITY`) for a filament without one. Prepare has the model only, so it takes each
|
||||||
|
part as a solid of the density of its filament, the part's own or else its object's. It reads each
|
||||||
|
filament's density from the filament's selected preset, edits not yet saved included, as slicing does:
|
||||||
|
the plater's own config holds the values of the filament edited last only. Preview has
|
||||||
|
what will be printed, so its markers come from the toolpaths, whose mass depends on walls, infill
|
||||||
|
and flow as well. There the solid markers are for what is printed up to the top layer the layer
|
||||||
|
slider shows: for the plate with brim, raft and supports, where the weight rests at that point of
|
||||||
|
the print; for an object or a body, its own extrusions. Each has a faded twin for the same at the
|
||||||
|
end of the print, so the slider shows the weight moving toward where it ends, and at the top layer
|
||||||
|
the two meet. In Prepare the plate has the model alone, as brim, raft and supports exist only once
|
||||||
|
sliced.
|
||||||
|
|
||||||
|
## Prepare: from the meshes
|
||||||
|
|
||||||
|
An object of one part takes the mass properties of its mesh at unit density, times its density, from
|
||||||
|
`its_mass_properties()`, which handles a mesh in a single pass. Each triangle and a fixed vertex of
|
||||||
|
the mesh span a tetrahedron whose signed volume is `V = a · (b × c) / 6`, with `a`, `b`, `c` taken
|
||||||
|
relative to that vertex. By the divergence theorem these volumes add up to the volume of a closed
|
||||||
|
mesh, their volume-weighted centroids to its center of mass, and their second moments
|
||||||
|
`V (a aᵀ + b bᵀ + c cᵀ + s sᵀ) / 20`, with `s = a + b + c`, to its own. Shells facing inward, such as
|
||||||
|
a cavity, subtract themselves, and flipping every triangle changes nothing. The sums are kept in
|
||||||
|
double precision and relative to a vertex of the mesh rather than the origin, which keeps them exact
|
||||||
|
for meshes far from it. The result keeps the spread of the mass about its center,
|
||||||
|
`(x - c)(x - c)ᵀ` averaged over the mass, from which the moments of inertia follow.
|
||||||
|
|
||||||
|
The CGAL routines that look alike do not compute this. `CGAL::centroid` weighs tetrahedra by their
|
||||||
|
unsigned volume, so it fails on cavities and on any shell that is not star-shaped from the fan's
|
||||||
|
apex; over triangles it returns the centroid of the surface, and over points the average of the
|
||||||
|
vertices, which depends on the tessellation. `CGAL::barycenter` with the signed volumes as weights
|
||||||
|
gives the same answer, but only after copying every tetrahedron into a vector of weighted points,
|
||||||
|
and takes two and a half times as long.
|
||||||
|
|
||||||
|
A mesh's result is in its own coordinates. Each `GLVolume` maps it to the world with its world
|
||||||
|
matrix `M`, and weighs it by the volume times the absolute determinant of that matrix: a center of
|
||||||
|
mass moves with any affine map, and the spread becomes `L S Lᵀ` for the linear part `L` of `M`, so no
|
||||||
|
mesh is ever transformed. The `GLVolume`'s matrices, rather than the
|
||||||
|
model's, let the markers follow an object while it is dragged, before the model is updated. Results
|
||||||
|
are cached by `ModelVolume` id; a `ModelVolume` takes a new id whenever its mesh changes, which is
|
||||||
|
the rule `reload_scene()` relies on to rebuild a `GLVolume`'s geometry, so a cached result never
|
||||||
|
outlives its mesh.
|
||||||
|
|
||||||
|
An assembly's parts overlap or touch, which the mesh formula would count twice, so `solid_bodies()`
|
||||||
|
slices them instead, in the object's coordinates. It cuts the height into 500 slabs, 100 while a part
|
||||||
|
is dragged, slices every part and negative volume at the middle of each slab, unites the parts and
|
||||||
|
cuts the negative volumes away, and links the islands of neighboring slabs that overlap into bodies
|
||||||
|
with `connected_bodies()`. Each island adds a prism of the slab's thickness at the density: its
|
||||||
|
area, and its first and second moments of area, from the same sums over the outline as the area,
|
||||||
|
with the slab's height for z. Where parts of different densities overlap, the later volume of the object counts, as
|
||||||
|
slicing clips every part by the parts after it; each part then weighs the region it prints, which is
|
||||||
|
credited to the island holding it. Each body also keeps the convex hull of its islands and the height
|
||||||
|
they span, whose corners, once transformed, give its bounding box, tight while the instance turns
|
||||||
|
about z only. The object is the sum of its bodies, its box that of its parts, as the object's size
|
||||||
|
shows it, and each plate the sum of the object instances `PartPlateList::find_instance()` puts on it,
|
||||||
|
so that an instance on no plate counts in none. The bodies are cached by `ModelObject` id with the volumes, types, densities and
|
||||||
|
transformations they were sliced from.
|
||||||
|
|
||||||
|
## Preview: from the toolpaths
|
||||||
|
|
||||||
|
`GCodeProcessor` sums the masses while it processes the G-code, in the same pass that builds the
|
||||||
|
moves, and leaves them in `GCodeProcessorResult`; `GCodeViewer` keeps a copy of them when it loads a
|
||||||
|
result. Nothing is stored per move.
|
||||||
|
|
||||||
|
Each extrusion weighs the volume of filament its E extrudes times the density of the filament that
|
||||||
|
extrudes it, so a print of several materials weighs each as it is. Flow ratio, line widths, ironing
|
||||||
|
and purging into infill all count
|
||||||
|
as printed. Its mass spreads evenly along the segment the bead's center runs, half the layer height
|
||||||
|
below the nozzle, in the frame of the stored moves: plate offset added, Z offset removed. Such a
|
||||||
|
segment from `a` to `b` adds `m (a + b) / 2` to the moments and `m (a² + a b + b²) / 3` to the second
|
||||||
|
moments along each axis, and its box widened by half the bead's height to the bounding box; not by
|
||||||
|
half its width, which the processor only estimates, so that a box runs along the walls' center lines. Arcs are already split into segments by the processor. Walls, infill, top and bottom surfaces, ironing
|
||||||
|
and gap fill make the parts. The brim and the support roles, the raft among them, count only in what
|
||||||
|
the plate prints. The skirt, the prime tower and custom G-code count nowhere.
|
||||||
|
|
||||||
|
The plate takes every extrusion, so it needs nothing more. The objects and bodies need the sliced
|
||||||
|
objects, which the G-code does not describe, so the G-code export hands the processor a locator
|
||||||
|
built from the `Print`; G-code opened from a file, or from a project sliced earlier, has no `Print`
|
||||||
|
behind it, and so shows the plate alone. Object labels would not do: profiles turn the four kinds
|
||||||
|
Orca writes on and off in every combination, and none of them tells the bodies apart.
|
||||||
|
|
||||||
|
The locator numbers the object instances and, for each assembly, the bodies of every instance. It
|
||||||
|
takes the bodies `PrintObject::prepare_infill()` found for separated infills, or, when that option
|
||||||
|
did not need them, links the islands (`Layer::lslices`) of neighboring layers into bodies with the
|
||||||
|
same `connected_bodies()`. For each extrusion of a part, it finds the first layer printed at or above
|
||||||
|
its height, as spiral vase rises through each layer, and the island holding it with an
|
||||||
|
`IslandLocator`, the one `solid_bodies()` credits its regions with: by the island's box, widened by
|
||||||
|
1 mm for walls reaching past it, with a polygon test only where boxes overlap, and the nearest
|
||||||
|
outline where none holds the point. The boxes of one layer's islands say nothing of the other
|
||||||
|
instances, so an instance whose widened box reaches another's, as copies placed side by side do,
|
||||||
|
tests the outlines alone, and outside them the nearest outline of all such instances wins. The island
|
||||||
|
gives both the instance and the body. The island found last is tried first, as extrusions mostly follow each other on one
|
||||||
|
island. Brim, raft and supports lie outside the islands. The brim counts in the plate only; a support
|
||||||
|
or raft extrusion goes to the instance whose footprint, the box of its widened islands, holds it, the
|
||||||
|
one whose center is nearest among several, or else the nearest footprint, as supports stand below and
|
||||||
|
around their object.
|
||||||
|
|
||||||
|
Each mass holds, for each layer id, the running total of what is printed up to that layer, the last
|
||||||
|
of which is the faded marker's, so the solid marker for any slider position is a single lookup. The layer ids are those
|
||||||
|
the moves carry, which are also the layers of libvgcode and of the slider; in a print by object they
|
||||||
|
follow the order of printing, so the solid markers show the objects printed so far as they are.
|
||||||
|
|
||||||
|
## Drawing
|
||||||
|
|
||||||
|
`smooth_sphere()` with a resolution divisible by four leaves every triangle within one octant, so it
|
||||||
|
splits into two models drawn with the `gouraud_light` shader in each kind's two colors. The radius is
|
||||||
|
9 pixels for the plate, 7 for the objects, 6 for the supports and 5 for the bodies, scaled like the canvas toolbar for the
|
||||||
|
display's DPI and kept constant on screen through the camera's inverse zoom. They are drawn in that
|
||||||
|
order, so that markers at one place show as rings. The faded markers are the same spheres at 40%
|
||||||
|
opacity, drawn before all the solid ones, which show over them where both meet.
|
||||||
|
|
||||||
|
The centers usually lie inside the objects, so the markers are drawn without the depth test and show
|
||||||
|
through the objects and anything in front of them. Back face culling keeps the far half of a sphere
|
||||||
|
from covering the near one. They are drawn after the ambient occlusion pass, which would otherwise
|
||||||
|
darken them as the surface behind them, and before FXAA, which smooths their edges.
|
||||||
|
|
||||||
|
The markers are part of the cached scene, so toggling them, or changing a filament's density while
|
||||||
|
they are shown, marks the scene dirty, and moving the layer slider redraws the scene with the solid
|
||||||
|
markers where they belong. In Prepare they are hidden while any gizmo other than Move, Rotate, Scale
|
||||||
|
and Lay on face is open, since the others work on the surface a marker would cover, and a hidden
|
||||||
|
object has no markers.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
The markers drawn last are kept, and a left click is tested against them before it selects: each
|
||||||
|
center and a point a radius to its right are projected to the screen, and the click hits a marker
|
||||||
|
within that distance. The solid markers are tested before the faded ones and the smaller kinds
|
||||||
|
before the larger, the order in which they cover each other. A hit opens the details of that marker
|
||||||
|
and keeps the click from changing the selection; a click anywhere else closes them. The box is an
|
||||||
|
ImGui window beside the marker, redrawn with the overlay from the markers of the last scene, so it
|
||||||
|
follows a dragged object, and in Preview the layer slider. It closes when its marker is gone, or when
|
||||||
|
the number of markers of its kind changes, as then it may stand for something else.
|
||||||
|
|
||||||
|
Its title says what the marker stands for: the plate, an object, an assembly or a part, the body of
|
||||||
|
an assembly. The G-code export lists the object instances for the processor, marking assemblies, as
|
||||||
|
it hands it the locator.
|
||||||
|
|
||||||
|
Each marker carries its sums: mass, volume, first moments and the second moments about the origin
|
||||||
|
along each axis, `Σ m x²`, `Σ m y²` and `Σ m z²`, which add up from parts to objects to plates. The
|
||||||
|
moment of inertia about the axis through the center parallel to x is then
|
||||||
|
`m (σy² + σz²)`, with `σ² = Σ m x² / m - c²` along each axis, and likewise for y and z. Masses are
|
||||||
|
kept in mg, volume times density in g/cm³, and shown in g, volumes in cm³ and moments of inertia in
|
||||||
|
g·mm². In Preview the box tells the finished print from what is printed up to the layer shown, the
|
||||||
|
two weighing differently, and both are placed in the bounding box of everything the marker holds
|
||||||
|
by the end.
|
||||||
@@ -18,8 +18,8 @@ each body on its own (see Octree infill).
|
|||||||
## Bodies
|
## Bodies
|
||||||
|
|
||||||
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
|
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
|
||||||
into 3D connected bodies before bridges are detected, so bridge anchors and
|
into 3D connected bodies with `connected_bodies()` before bridges are detected,
|
||||||
printed infill share one origin. Islands on adjacent layers belong to one body
|
so bridge anchors and printed infill share one origin. Islands on adjacent layers belong to one body
|
||||||
when their slices overlap. Parts that touch or overlap form one body. Separate
|
when their slices overlap. Parts that touch or overlap form one body. Separate
|
||||||
parts, disconnected islands of one mesh, and interleaved parts that never touch,
|
parts, disconnected islands of one mesh, and interleaved parts that never touch,
|
||||||
such as chain links, each form their own. Every island stores the index of its
|
such as chain links, each form their own. Every island stores the index of its
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000"
|
"4000"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000",
|
"4000",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000"
|
"4000"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000"
|
"4000"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000",
|
"4000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000",
|
"4000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000",
|
"10000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"30",
|
"30",
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000",
|
"10000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"4000",
|
"4000",
|
||||||
"4000",
|
"4000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000",
|
"10000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"30",
|
"30",
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000",
|
"10000",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"30",
|
"30",
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000"
|
"10000"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"50",
|
"50",
|
||||||
"50"
|
"50"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000",
|
"10000",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"30",
|
"30",
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000",
|
"10000",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"30",
|
"30",
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000"
|
"10000"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"30",
|
"30",
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
|
"enable_arc_fitting": "0",
|
||||||
"default_acceleration": [
|
"default_acceleration": [
|
||||||
"10000",
|
"10000",
|
||||||
"10000"
|
"10000"
|
||||||
|
|||||||
@@ -2,5 +2,4 @@
|
|||||||
#add_subdirectory(openvdb)
|
#add_subdirectory(openvdb)
|
||||||
# add_subdirectory(meshboolean)
|
# add_subdirectory(meshboolean)
|
||||||
add_subdirectory(its_neighbor_index)
|
add_subdirectory(its_neighbor_index)
|
||||||
# add_subdirectory(opencsg)
|
|
||||||
#add_subdirectory(aabb-evaluation)
|
#add_subdirectory(aabb-evaluation)
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
|
||||||
|
|
||||||
project(OpenCSG-example)
|
|
||||||
|
|
||||||
add_executable(opencsg_example WIN32
|
|
||||||
main.cpp
|
|
||||||
Engine.hpp Engine.cpp
|
|
||||||
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/Jobs/Job.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
|
|
||||||
|
|
||||||
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
|
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
find_package(GLEW REQUIRED)
|
|
||||||
find_package(OpenCSG REQUIRED)
|
|
||||||
include(${wxWidgets_USE_FILE})
|
|
||||||
|
|
||||||
target_link_libraries(opencsg_example libslic3r)
|
|
||||||
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
|
|
||||||
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
|
|
||||||
|
|
||||||
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
|
|
||||||
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
|
|
||||||
OpenCSG::opencsg
|
|
||||||
GLEW::GLEW
|
|
||||||
OpenGL::GL
|
|
||||||
#-lXrandr -lXext -lX11
|
|
||||||
)
|
|
||||||
@@ -1,495 +0,0 @@
|
|||||||
#include "Engine.hpp"
|
|
||||||
#include <libslic3r/Utils.hpp>
|
|
||||||
#include <libslic3r/SLAPrint.hpp>
|
|
||||||
|
|
||||||
#include <GL/glew.h>
|
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define HAS_GLSAFE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAS_GLSAFE
|
|
||||||
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
|
|
||||||
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
|
|
||||||
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
|
||||||
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
|
||||||
|
|
||||||
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
|
|
||||||
{
|
|
||||||
GLenum err = glGetError();
|
|
||||||
if (err == GL_NO_ERROR)
|
|
||||||
return;
|
|
||||||
const char *sErr = 0;
|
|
||||||
switch (err) {
|
|
||||||
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
|
|
||||||
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
|
|
||||||
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
|
|
||||||
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
|
|
||||||
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
|
|
||||||
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
|
|
||||||
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
|
|
||||||
default: sErr = "Unknown"; break;
|
|
||||||
}
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
inline void glAssertRecentCall() { }
|
|
||||||
#define glsafe(cmd) cmd
|
|
||||||
#define glcheck()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Slic3r { namespace GL {
|
|
||||||
|
|
||||||
Scene::Scene() = default;
|
|
||||||
Scene::~Scene() = default;
|
|
||||||
|
|
||||||
void CSGDisplay::render_scene()
|
|
||||||
{
|
|
||||||
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
|
||||||
glsafe(::glColor4fv(color));
|
|
||||||
|
|
||||||
if (m_csgsettings.is_enabled()) {
|
|
||||||
OpenCSG::render(m_scene_cache.primitives_csg);
|
|
||||||
glDepthFunc(GL_EQUAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& p : m_scene_cache.primitives_csg) p->render();
|
|
||||||
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
|
|
||||||
|
|
||||||
for (auto& p : m_scene_cache.primitives_free) p->render();
|
|
||||||
|
|
||||||
glFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scene::set_print(std::unique_ptr<SLAPrint> &&print)
|
|
||||||
{
|
|
||||||
m_print = std::move(print);
|
|
||||||
|
|
||||||
// Notify displays
|
|
||||||
call(&Listener::on_scene_updated, m_listeners, *this);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoundingBoxf3 Scene::get_bounding_box() const
|
|
||||||
{
|
|
||||||
return m_print->model().bounding_box();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSGDisplay::SceneCache::clear()
|
|
||||||
{
|
|
||||||
primitives_csg.clear();
|
|
||||||
primitives_free.clear();
|
|
||||||
primitives.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
|
|
||||||
{
|
|
||||||
auto p = std::make_shared<Primitive>();
|
|
||||||
p->load_mesh(mesh);
|
|
||||||
primitives.emplace_back(p);
|
|
||||||
primitives_free.emplace_back(p.get());
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
|
|
||||||
OpenCSG::Operation o,
|
|
||||||
unsigned c)
|
|
||||||
{
|
|
||||||
auto p = std::make_shared<Primitive>(o, c);
|
|
||||||
p->load_mesh(mesh);
|
|
||||||
primitives.emplace_back(p);
|
|
||||||
primitives_csg.emplace_back(p.get());
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
|
|
||||||
{
|
|
||||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
|
||||||
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
|
|
||||||
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
|
|
||||||
this->vertices_and_normals_interleaved.emplace_back(nx);
|
|
||||||
this->vertices_and_normals_interleaved.emplace_back(ny);
|
|
||||||
this->vertices_and_normals_interleaved.emplace_back(nz);
|
|
||||||
this->vertices_and_normals_interleaved.emplace_back(x);
|
|
||||||
this->vertices_and_normals_interleaved.emplace_back(y);
|
|
||||||
this->vertices_and_normals_interleaved.emplace_back(z);
|
|
||||||
|
|
||||||
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
|
|
||||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
|
||||||
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
|
|
||||||
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
|
|
||||||
this->triangle_indices.emplace_back(idx1);
|
|
||||||
this->triangle_indices.emplace_back(idx2);
|
|
||||||
this->triangle_indices.emplace_back(idx3);
|
|
||||||
this->triangle_indices_size = this->triangle_indices.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
|
|
||||||
{
|
|
||||||
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
|
|
||||||
assert(quad_indices.empty() && triangle_indices_size == 0);
|
|
||||||
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
|
|
||||||
|
|
||||||
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
|
|
||||||
|
|
||||||
int vertices_count = 0;
|
|
||||||
for (size_t i = 0; i < mesh.facets_count(); ++i) {
|
|
||||||
const stl_facet &facet = mesh.stl.facet_start[i];
|
|
||||||
for (int j = 0; j < 3; ++j)
|
|
||||||
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
|
|
||||||
|
|
||||||
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
|
|
||||||
vertices_count += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::finalize_geometry()
|
|
||||||
{
|
|
||||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
|
||||||
assert(this->triangle_indices_VBO_id == 0);
|
|
||||||
assert(this->quad_indices_VBO_id == 0);
|
|
||||||
|
|
||||||
if (!this->vertices_and_normals_interleaved.empty()) {
|
|
||||||
glsafe(
|
|
||||||
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
|
||||||
this->vertices_and_normals_interleaved_VBO_id));
|
|
||||||
glsafe(
|
|
||||||
::glBufferData(GL_ARRAY_BUFFER,
|
|
||||||
GLsizeiptr(
|
|
||||||
this->vertices_and_normals_interleaved.size() *
|
|
||||||
4),
|
|
||||||
this->vertices_and_normals_interleaved.data(),
|
|
||||||
GL_STATIC_DRAW));
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
||||||
this->vertices_and_normals_interleaved.clear();
|
|
||||||
}
|
|
||||||
if (!this->triangle_indices.empty()) {
|
|
||||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
this->triangle_indices_VBO_id));
|
|
||||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
GLsizeiptr(this->triangle_indices.size() * 4),
|
|
||||||
this->triangle_indices.data(), GL_STATIC_DRAW));
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
||||||
this->triangle_indices.clear();
|
|
||||||
}
|
|
||||||
if (!this->quad_indices.empty()) {
|
|
||||||
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
this->quad_indices_VBO_id));
|
|
||||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
GLsizeiptr(this->quad_indices.size() * 4),
|
|
||||||
this->quad_indices.data(), GL_STATIC_DRAW));
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
||||||
this->quad_indices.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::release_geometry()
|
|
||||||
{
|
|
||||||
if (this->vertices_and_normals_interleaved_VBO_id) {
|
|
||||||
glsafe(
|
|
||||||
::glDeleteBuffers(1,
|
|
||||||
&this->vertices_and_normals_interleaved_VBO_id));
|
|
||||||
this->vertices_and_normals_interleaved_VBO_id = 0;
|
|
||||||
}
|
|
||||||
if (this->triangle_indices_VBO_id) {
|
|
||||||
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
|
|
||||||
this->triangle_indices_VBO_id = 0;
|
|
||||||
}
|
|
||||||
if (this->quad_indices_VBO_id) {
|
|
||||||
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
|
|
||||||
this->quad_indices_VBO_id = 0;
|
|
||||||
}
|
|
||||||
this->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::render() const
|
|
||||||
{
|
|
||||||
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
|
|
||||||
assert(this->triangle_indices_VBO_id != 0 ||
|
|
||||||
this->quad_indices_VBO_id != 0);
|
|
||||||
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
|
||||||
this->vertices_and_normals_interleaved_VBO_id));
|
|
||||||
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
|
|
||||||
reinterpret_cast<const void *>(3 * sizeof(float))));
|
|
||||||
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
|
|
||||||
|
|
||||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
|
||||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
|
||||||
|
|
||||||
// Render using the Vertex Buffer Objects.
|
|
||||||
if (this->triangle_indices_size > 0) {
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
this->triangle_indices_VBO_id));
|
|
||||||
glsafe(::glDrawElements(GL_TRIANGLES,
|
|
||||||
GLsizei(this->triangle_indices_size),
|
|
||||||
GL_UNSIGNED_INT, nullptr));
|
|
||||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
||||||
}
|
|
||||||
if (this->quad_indices_size > 0) {
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
this->quad_indices_VBO_id));
|
|
||||||
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
|
|
||||||
GL_UNSIGNED_INT, nullptr));
|
|
||||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
||||||
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
|
||||||
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::clear() {
|
|
||||||
this->vertices_and_normals_interleaved.clear();
|
|
||||||
this->triangle_indices.clear();
|
|
||||||
this->quad_indices.clear();
|
|
||||||
vertices_and_normals_interleaved_size = 0;
|
|
||||||
triangle_indices_size = 0;
|
|
||||||
quad_indices_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexedVertexArray::shrink_to_fit() {
|
|
||||||
this->vertices_and_normals_interleaved.shrink_to_fit();
|
|
||||||
this->triangle_indices.shrink_to_fit();
|
|
||||||
this->quad_indices.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Volume::render()
|
|
||||||
{
|
|
||||||
glsafe(::glPushMatrix());
|
|
||||||
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
|
|
||||||
m_geom.render();
|
|
||||||
glsafe(::glPopMatrix());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::clear_screen()
|
|
||||||
{
|
|
||||||
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
Display::~Display()
|
|
||||||
{
|
|
||||||
OpenCSG::freeResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::set_active(long width, long height)
|
|
||||||
{
|
|
||||||
if (!m_initialized) {
|
|
||||||
glewInit();
|
|
||||||
m_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// gray background
|
|
||||||
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
|
|
||||||
|
|
||||||
// Enable two OpenGL lights
|
|
||||||
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
|
|
||||||
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
|
|
||||||
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
|
|
||||||
|
|
||||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
|
|
||||||
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
|
|
||||||
glEnable(GL_LIGHT0);
|
|
||||||
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
|
|
||||||
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
|
|
||||||
glEnable(GL_LIGHT1);
|
|
||||||
glEnable(GL_LIGHTING);
|
|
||||||
glEnable(GL_NORMALIZE);
|
|
||||||
|
|
||||||
// Use depth buffering for hidden surface elimination
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glEnable(GL_STENCIL_TEST);
|
|
||||||
|
|
||||||
set_screen_size(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::set_screen_size(long width, long height)
|
|
||||||
{
|
|
||||||
if (m_size.x() != width || m_size.y() != height)
|
|
||||||
m_camera->set_screen(width, height);
|
|
||||||
|
|
||||||
m_size = {width, height};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::repaint()
|
|
||||||
{
|
|
||||||
clear_screen();
|
|
||||||
|
|
||||||
m_camera->view();
|
|
||||||
render_scene();
|
|
||||||
|
|
||||||
m_fps_counter.update();
|
|
||||||
|
|
||||||
swap_buffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::on_scene_updated(const Scene &scene)
|
|
||||||
{
|
|
||||||
const SLAPrint *print = scene.get_print();
|
|
||||||
if (!print) return;
|
|
||||||
|
|
||||||
auto bb = scene.get_bounding_box();
|
|
||||||
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
|
|
||||||
m_wheel_pos = long(2 * d);
|
|
||||||
|
|
||||||
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
|
||||||
call(&Display::on_scene_updated, m_displays, scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
|
|
||||||
{
|
|
||||||
m_wheel_pos += v / d;
|
|
||||||
|
|
||||||
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
|
||||||
call(&Display::repaint, m_displays);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::on_moved_to(long x, long y)
|
|
||||||
{
|
|
||||||
if (m_left_btn) {
|
|
||||||
call_cameras(&Camera::rotate, (Vec2i32{x, y} - m_mouse_pos).cast<float>());
|
|
||||||
call(&Display::repaint, m_displays);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_mouse_pos = {x, y};
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
|
|
||||||
{
|
|
||||||
using namespace OpenCSG;
|
|
||||||
|
|
||||||
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
|
|
||||||
|
|
||||||
m_csgsettings = settings;
|
|
||||||
setOption(AlgorithmSetting, m_csgsettings.get_algo());
|
|
||||||
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
|
|
||||||
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
|
|
||||||
|
|
||||||
if (needupdate) {
|
|
||||||
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
|
|
||||||
if (p->getConvexity() > 1)
|
|
||||||
p->setConvexity(m_csgsettings.get_convexity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSGDisplay::on_scene_updated(const Scene &scene)
|
|
||||||
{
|
|
||||||
const SLAPrint *print = scene.get_print();
|
|
||||||
if (!print) return;
|
|
||||||
|
|
||||||
m_scene_cache.clear();
|
|
||||||
|
|
||||||
for (const SLAPrintObject *po : print->objects()) {
|
|
||||||
const ModelObject *mo = po->model_object();
|
|
||||||
TriangleMesh msh = mo->raw_mesh();
|
|
||||||
|
|
||||||
sla::DrainHoles holedata = mo->sla_drain_holes;
|
|
||||||
|
|
||||||
for (const ModelInstance *mi : mo->instances) {
|
|
||||||
|
|
||||||
TriangleMesh mshinst = msh;
|
|
||||||
auto interior = po->hollowed_interior_mesh();
|
|
||||||
interior.transform(po->trafo().inverse());
|
|
||||||
|
|
||||||
mshinst.merge(interior);
|
|
||||||
|
|
||||||
mi->transform_mesh(&mshinst);
|
|
||||||
|
|
||||||
auto bb = mshinst.bounding_box();
|
|
||||||
auto center = bb.center().cast<float>();
|
|
||||||
mshinst.translate(-center);
|
|
||||||
|
|
||||||
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
|
|
||||||
m_csgsettings.get_convexity());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const sla::DrainHole &holept : holedata) {
|
|
||||||
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
|
||||||
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Camera::view()
|
|
||||||
{
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glLoadIdentity();
|
|
||||||
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
|
|
||||||
m_referene.x(), m_referene.y(), m_referene.z(),
|
|
||||||
0.0, 0.0, 1.0); /* up is in positive Y direction */
|
|
||||||
|
|
||||||
// TODO Could have been set in prevoius gluLookAt in first argument
|
|
||||||
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
|
|
||||||
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
|
|
||||||
|
|
||||||
if (m_clip_z > 0.) {
|
|
||||||
GLdouble plane[] = {0., 0., 1., m_clip_z};
|
|
||||||
glClipPlane(GL_CLIP_PLANE0, plane);
|
|
||||||
glEnable(GL_CLIP_PLANE0);
|
|
||||||
} else {
|
|
||||||
glDisable(GL_CLIP_PLANE0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PerspectiveCamera::set_screen(long width, long height)
|
|
||||||
{
|
|
||||||
// Setup the view of the CSG shape
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glLoadIdentity();
|
|
||||||
gluPerspective(45.0, width / double(height), .1, 200.0);
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool enable_multisampling(bool e)
|
|
||||||
{
|
|
||||||
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
|
|
||||||
|
|
||||||
GLint is_ms_context;
|
|
||||||
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
|
|
||||||
|
|
||||||
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseInput::Listener::~Listener() = default;
|
|
||||||
|
|
||||||
void FpsCounter::update()
|
|
||||||
{
|
|
||||||
++m_frames;
|
|
||||||
|
|
||||||
TimePoint msec = Clock::now();
|
|
||||||
|
|
||||||
double seconds_window = to_sec(msec - m_window);
|
|
||||||
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
|
|
||||||
|
|
||||||
if (to_sec(msec - m_last) >= m_resolution) {
|
|
||||||
m_last = msec;
|
|
||||||
for (auto &l : m_listeners) l(m_fps);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seconds_window >= m_window_size) {
|
|
||||||
m_frames = 0;
|
|
||||||
m_window = msec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}} // namespace Slic3r::GL
|
|
||||||
@@ -1,488 +0,0 @@
|
|||||||
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
|
|
||||||
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include <libslic3r/Geometry.hpp>
|
|
||||||
#include <libslic3r/Model.hpp>
|
|
||||||
#include <libslic3r/TriangleMesh.hpp>
|
|
||||||
#include <libslic3r/SLA/Hollowing.hpp>
|
|
||||||
#include <opencsg/opencsg.h>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
class SLAPrint;
|
|
||||||
|
|
||||||
namespace GL {
|
|
||||||
|
|
||||||
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
|
|
||||||
|
|
||||||
// remove empty weak pointers from a vector
|
|
||||||
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
|
|
||||||
auto it = std::remove_if(listeners.begin(), listeners.end(),
|
|
||||||
[](auto &l) { return !l.lock(); });
|
|
||||||
listeners.erase(it, listeners.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call a class method on each element of a vector of objects (weak pointers)
|
|
||||||
// of the same type.
|
|
||||||
template<class F, class L, class...Args>
|
|
||||||
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
|
|
||||||
for (auto &l : listeners)
|
|
||||||
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A representation of a mouse input for the engine.
|
|
||||||
class MouseInput
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum WheelAxis { waVertical, waHorizontal };
|
|
||||||
|
|
||||||
// Interface to implement if an object wants to receive notifications
|
|
||||||
// about mouse events.
|
|
||||||
class Listener {
|
|
||||||
public:
|
|
||||||
virtual ~Listener();
|
|
||||||
|
|
||||||
virtual void on_left_click_down() {}
|
|
||||||
virtual void on_left_click_up() {}
|
|
||||||
virtual void on_right_click_down() {}
|
|
||||||
virtual void on_right_click_up() {}
|
|
||||||
virtual void on_double_click() {}
|
|
||||||
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
|
|
||||||
virtual void on_moved_to(long /*x*/, long /*y*/) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
vector<std::weak_ptr<Listener>> m_listeners;
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual ~MouseInput() = default;
|
|
||||||
|
|
||||||
virtual void left_click_down()
|
|
||||||
{
|
|
||||||
call(&Listener::on_left_click_down, m_listeners);
|
|
||||||
}
|
|
||||||
virtual void left_click_up()
|
|
||||||
{
|
|
||||||
call(&Listener::on_left_click_up, m_listeners);
|
|
||||||
}
|
|
||||||
virtual void right_click_down()
|
|
||||||
{
|
|
||||||
call(&Listener::on_right_click_down, m_listeners);
|
|
||||||
}
|
|
||||||
virtual void right_click_up()
|
|
||||||
{
|
|
||||||
call(&Listener::on_right_click_up, m_listeners);
|
|
||||||
}
|
|
||||||
virtual void double_click()
|
|
||||||
{
|
|
||||||
call(&Listener::on_double_click, m_listeners);
|
|
||||||
}
|
|
||||||
virtual void scroll(long v, long d, WheelAxis wa)
|
|
||||||
{
|
|
||||||
call(&Listener::on_scroll, m_listeners, v, d, wa);
|
|
||||||
}
|
|
||||||
virtual void move_to(long x, long y)
|
|
||||||
{
|
|
||||||
call(&Listener::on_moved_to, m_listeners, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_listener(std::shared_ptr<Listener> listener)
|
|
||||||
{
|
|
||||||
m_listeners.emplace_back(listener);
|
|
||||||
cleanup(m_listeners);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is a stripped down version of Slic3r::IndexedVertexArray
|
|
||||||
class IndexedVertexArray {
|
|
||||||
public:
|
|
||||||
~IndexedVertexArray() { release_geometry(); }
|
|
||||||
|
|
||||||
// Vertices and their normals, interleaved to be used by void
|
|
||||||
// glInterleavedArrays(GL_N3F_V3F, 0, x)
|
|
||||||
vector<float> vertices_and_normals_interleaved;
|
|
||||||
vector<int> triangle_indices;
|
|
||||||
vector<int> quad_indices;
|
|
||||||
|
|
||||||
// When the geometry data is loaded into the graphics card as Vertex
|
|
||||||
// Buffer Objects, the above mentioned std::vectors are cleared and the
|
|
||||||
// following variables keep their original length.
|
|
||||||
size_t vertices_and_normals_interleaved_size{ 0 };
|
|
||||||
size_t triangle_indices_size{ 0 };
|
|
||||||
size_t quad_indices_size{ 0 };
|
|
||||||
|
|
||||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
|
||||||
// Zero if the VBOs are not sent to GPU yet.
|
|
||||||
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
|
|
||||||
unsigned int triangle_indices_VBO_id{ 0 };
|
|
||||||
unsigned int quad_indices_VBO_id{ 0 };
|
|
||||||
|
|
||||||
|
|
||||||
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
|
|
||||||
|
|
||||||
inline void push_geometry(
|
|
||||||
double x, double y, double z, double nx, double ny, double nz)
|
|
||||||
{
|
|
||||||
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void push_geometry(const Vec3d &p, const Vec3d &n)
|
|
||||||
{
|
|
||||||
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void push_triangle(int idx1, int idx2, int idx3);
|
|
||||||
|
|
||||||
void load_mesh(const TriangleMesh &mesh);
|
|
||||||
|
|
||||||
inline bool has_VBOs() const
|
|
||||||
{
|
|
||||||
return vertices_and_normals_interleaved_VBO_id != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize the initialization of the geometry & indices,
|
|
||||||
// upload the geometry and indices to OpenGL VBO objects
|
|
||||||
// and shrink the allocated data, possibly relasing it if it has been
|
|
||||||
// loaded into the VBOs.
|
|
||||||
void finalize_geometry();
|
|
||||||
// Release the geometry data, release OpenGL VBOs.
|
|
||||||
void release_geometry();
|
|
||||||
|
|
||||||
void render() const;
|
|
||||||
|
|
||||||
// Is there any geometry data stored?
|
|
||||||
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// Shrink the internal storage to tighly fit the data stored.
|
|
||||||
void shrink_to_fit();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try to enable or disable multisampling.
|
|
||||||
bool enable_multisampling(bool e = true);
|
|
||||||
|
|
||||||
class Volume {
|
|
||||||
IndexedVertexArray m_geom;
|
|
||||||
Geometry::Transformation m_trafo;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
void render();
|
|
||||||
|
|
||||||
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
|
|
||||||
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
|
|
||||||
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
|
|
||||||
void scale(double s) { scale({s, s, s}); }
|
|
||||||
|
|
||||||
inline void load_mesh(const TriangleMesh &mesh)
|
|
||||||
{
|
|
||||||
m_geom.load_mesh(mesh);
|
|
||||||
m_geom.finalize_geometry();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// A primitive that can be used with OpenCSG rendering algorithms.
|
|
||||||
// Does a similar job to GLVolume.
|
|
||||||
class Primitive : public Volume, public OpenCSG::Primitive
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using OpenCSG::Primitive::Primitive;
|
|
||||||
|
|
||||||
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
|
|
||||||
|
|
||||||
void render() override { Volume::render(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// A simple representation of a camera in a 3D scene
|
|
||||||
class Camera {
|
|
||||||
protected:
|
|
||||||
Vec2f m_rot = {0., 0.};
|
|
||||||
Vec3d m_referene = {0., 0., 0.};
|
|
||||||
double m_zoom = 0.;
|
|
||||||
double m_clip_z = 0.;
|
|
||||||
public:
|
|
||||||
|
|
||||||
virtual ~Camera() = default;
|
|
||||||
|
|
||||||
virtual void view();
|
|
||||||
virtual void set_screen(long width, long height) = 0;
|
|
||||||
|
|
||||||
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
|
|
||||||
void rotate(const Vec2f &rotation) { m_rot += rotation; }
|
|
||||||
void set_zoom(double z) { m_zoom = z; }
|
|
||||||
void set_reference_point(const Vec3d &p) { m_referene = p; }
|
|
||||||
void set_clip_z(double z) { m_clip_z = z; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reset a camera object
|
|
||||||
inline void reset(Camera &cam)
|
|
||||||
{
|
|
||||||
cam.set_rotation({0., 0.});
|
|
||||||
cam.set_zoom(0.);
|
|
||||||
cam.set_reference_point({0., 0., 0.});
|
|
||||||
cam.set_clip_z(0.);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specialization of a camera which shows in perspective projection
|
|
||||||
class PerspectiveCamera: public Camera {
|
|
||||||
public:
|
|
||||||
|
|
||||||
void set_screen(long width, long height) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A simple counter of FPS. Subscribed objects will receive updates of the
|
|
||||||
// current fps.
|
|
||||||
class FpsCounter {
|
|
||||||
vector<std::function<void(double)>> m_listeners;
|
|
||||||
|
|
||||||
using Clock = std::chrono::high_resolution_clock;
|
|
||||||
using Duration = Clock::duration;
|
|
||||||
using TimePoint = Clock::time_point;
|
|
||||||
|
|
||||||
int m_frames = 0;
|
|
||||||
TimePoint m_last = Clock::now(), m_window = m_last;
|
|
||||||
|
|
||||||
double m_resolution = 0.1, m_window_size = 1.0;
|
|
||||||
double m_fps = 0.;
|
|
||||||
|
|
||||||
static double to_sec(Duration d)
|
|
||||||
{
|
|
||||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
void update();
|
|
||||||
|
|
||||||
void add_listener(std::function<void(double)> lst)
|
|
||||||
{
|
|
||||||
m_listeners.emplace_back(lst);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear_listeners() { m_listeners = {}; }
|
|
||||||
|
|
||||||
void set_notification_interval(double seconds);
|
|
||||||
void set_measure_window_size(double seconds);
|
|
||||||
|
|
||||||
double get_notification_interval() const { return m_resolution; }
|
|
||||||
double get_mesure_window_size() const { return m_window_size; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Collection of the used OpenCSG library settings.
|
|
||||||
class CSGSettings {
|
|
||||||
public:
|
|
||||||
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
|
|
||||||
|
|
||||||
private:
|
|
||||||
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
|
|
||||||
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
|
|
||||||
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
|
|
||||||
bool m_enable = true;
|
|
||||||
unsigned int m_convexity = DEFAULT_CONVEXITY;
|
|
||||||
|
|
||||||
public:
|
|
||||||
int get_algo() const { return int(m_csgalg); }
|
|
||||||
void set_algo(int alg)
|
|
||||||
{
|
|
||||||
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
|
|
||||||
m_csgalg = OpenCSG::Algorithm(alg);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_depth_algo() const { return int(m_depth_algo); }
|
|
||||||
void set_depth_algo(int alg)
|
|
||||||
{
|
|
||||||
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
|
|
||||||
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_optimization() const { return int(m_optim); }
|
|
||||||
void set_optimization(int o)
|
|
||||||
{
|
|
||||||
if (o < OpenCSG::Optimization::OptimizationUnused)
|
|
||||||
m_optim = OpenCSG::Optimization(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
void enable_csg(bool en = true) { m_enable = en; }
|
|
||||||
bool is_enabled() const { return m_enable; }
|
|
||||||
|
|
||||||
unsigned get_convexity() const { return m_convexity; }
|
|
||||||
void set_convexity(unsigned c) { m_convexity = c; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
|
|
||||||
class Scene
|
|
||||||
{
|
|
||||||
std::unique_ptr<SLAPrint> m_print;
|
|
||||||
public:
|
|
||||||
|
|
||||||
// Subscribers will be notified if the model is changed. This might be a
|
|
||||||
// display which will have to load the meshes and repaint itself when
|
|
||||||
// the scene data changes.
|
|
||||||
// eg. We load a new 3mf through the UI, this will notify the controller
|
|
||||||
// associated with the scene and all the displays that the controller is
|
|
||||||
// connected with.
|
|
||||||
class Listener {
|
|
||||||
public:
|
|
||||||
virtual ~Listener() = default;
|
|
||||||
virtual void on_scene_updated(const Scene &scene) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Scene();
|
|
||||||
~Scene();
|
|
||||||
|
|
||||||
void set_print(std::unique_ptr<SLAPrint> &&print);
|
|
||||||
const SLAPrint * get_print() const { return m_print.get(); }
|
|
||||||
|
|
||||||
BoundingBoxf3 get_bounding_box() const;
|
|
||||||
|
|
||||||
void add_listener(std::shared_ptr<Listener> listener)
|
|
||||||
{
|
|
||||||
m_listeners.emplace_back(listener);
|
|
||||||
cleanup(m_listeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
vector<std::weak_ptr<Listener>> m_listeners;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The basic Display. This is almost just an interface but will do all the
|
|
||||||
// initialization and show the fps values. Overriding the render_scene is
|
|
||||||
// needed to show the scene content. The specific method of displaying the
|
|
||||||
// scene is up the particular implementation (OpenCSG or other screen space
|
|
||||||
// boolean algorithms)
|
|
||||||
class Display : public Scene::Listener
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
Vec2i32 m_size;
|
|
||||||
bool m_initialized = false;
|
|
||||||
|
|
||||||
std::shared_ptr<Camera> m_camera;
|
|
||||||
FpsCounter m_fps_counter;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Display(std::shared_ptr<Camera> camera = nullptr)
|
|
||||||
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
|
|
||||||
{}
|
|
||||||
|
|
||||||
~Display() override;
|
|
||||||
|
|
||||||
std::shared_ptr<const Camera> get_camera() const { return m_camera; }
|
|
||||||
std::shared_ptr<Camera> get_camera() { return m_camera; }
|
|
||||||
void set_camera(std::shared_ptr<Camera> cam) { m_camera = cam; }
|
|
||||||
|
|
||||||
virtual void swap_buffers() = 0;
|
|
||||||
virtual void set_active(long width, long height);
|
|
||||||
virtual void set_screen_size(long width, long height);
|
|
||||||
Vec2i32 get_screen_size() const { return m_size; }
|
|
||||||
|
|
||||||
virtual void repaint();
|
|
||||||
|
|
||||||
bool is_initialized() const { return m_initialized; }
|
|
||||||
|
|
||||||
virtual void clear_screen();
|
|
||||||
virtual void render_scene() {}
|
|
||||||
|
|
||||||
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
|
|
||||||
{
|
|
||||||
m_fps_counter = std::forward<_FpsCounter>(fpsc);
|
|
||||||
}
|
|
||||||
|
|
||||||
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
|
|
||||||
FpsCounter &get_fps_counter() { return m_fps_counter; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Special dispaly using OpenCSG for rendering the scene.
|
|
||||||
class CSGDisplay : public Display {
|
|
||||||
protected:
|
|
||||||
CSGSettings m_csgsettings;
|
|
||||||
|
|
||||||
// Cache the renderable primitives. These will be fetched when the scene
|
|
||||||
// is modified.
|
|
||||||
struct SceneCache {
|
|
||||||
vector<std::shared_ptr<Primitive>> primitives;
|
|
||||||
vector<Primitive *> primitives_free;
|
|
||||||
vector<OpenCSG::Primitive *> primitives_csg;
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh);
|
|
||||||
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh,
|
|
||||||
OpenCSG::Operation op,
|
|
||||||
unsigned covexity);
|
|
||||||
} m_scene_cache;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
// Receive or apply the new settings.
|
|
||||||
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
|
|
||||||
void apply_csgsettings(const CSGSettings &settings);
|
|
||||||
|
|
||||||
void render_scene() override;
|
|
||||||
|
|
||||||
void on_scene_updated(const Scene &scene) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// The controller is a hub which dispatches mouse events to the connected
|
|
||||||
// displays. It keeps track of the mouse wheel position, the states whether
|
|
||||||
// the mouse is being held, dragged, etc... All the connected displays will
|
|
||||||
// mirror the camera movement (if there is more than one display).
|
|
||||||
class Controller : public std::enable_shared_from_this<Controller>,
|
|
||||||
public MouseInput::Listener,
|
|
||||||
public Scene::Listener
|
|
||||||
{
|
|
||||||
long m_wheel_pos = 0;
|
|
||||||
Vec2i32 m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
|
|
||||||
bool m_left_btn = false, m_right_btn = false;
|
|
||||||
|
|
||||||
std::shared_ptr<Scene> m_scene;
|
|
||||||
vector<std::weak_ptr<Display>> m_displays;
|
|
||||||
|
|
||||||
// Call a method of Camera on all the cameras of the attached displays
|
|
||||||
template<class F, class...Args>
|
|
||||||
void call_cameras(F &&f, Args&&... args) {
|
|
||||||
for (std::weak_ptr<Display> &l : m_displays)
|
|
||||||
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
|
|
||||||
(cam.get()->*f)(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
// Set the scene that will be controlled.
|
|
||||||
void set_scene(std::shared_ptr<Scene> scene)
|
|
||||||
{
|
|
||||||
m_scene = scene;
|
|
||||||
m_scene->add_listener(shared_from_this());
|
|
||||||
}
|
|
||||||
|
|
||||||
const Scene * get_scene() const { return m_scene.get(); }
|
|
||||||
|
|
||||||
void add_display(std::shared_ptr<Display> disp)
|
|
||||||
{
|
|
||||||
m_displays.emplace_back(disp);
|
|
||||||
cleanup(m_displays);
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_displays() { m_displays = {}; }
|
|
||||||
|
|
||||||
void on_scene_updated(const Scene &scene) override;
|
|
||||||
|
|
||||||
void on_left_click_down() override { m_left_btn = true; }
|
|
||||||
void on_left_click_up() override { m_left_btn = false; }
|
|
||||||
void on_right_click_down() override { m_right_btn = true; }
|
|
||||||
void on_right_click_up() override { m_right_btn = false; }
|
|
||||||
|
|
||||||
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
|
|
||||||
void on_moved_to(long x, long y) override;
|
|
||||||
|
|
||||||
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
|
|
||||||
};
|
|
||||||
|
|
||||||
}} // namespace Slic3r::GL
|
|
||||||
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#include "ShaderCSGDisplay.hpp"
|
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
|
||||||
#include <GL/glew.h>
|
|
||||||
|
|
||||||
namespace Slic3r { namespace GL {
|
|
||||||
|
|
||||||
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
|
|
||||||
{
|
|
||||||
auto v = std::make_shared<CSGVolume>();
|
|
||||||
v->load_mesh(mesh);
|
|
||||||
m_volumes.emplace_back(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCSGDisplay::render_scene()
|
|
||||||
{
|
|
||||||
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
|
||||||
glColor4fv(color);
|
|
||||||
glDepthFunc(GL_LESS);
|
|
||||||
for (auto &v : m_volumes) v->render();
|
|
||||||
glFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
|
|
||||||
{
|
|
||||||
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
|
|
||||||
// Look at CSGDisplay::on_scene_updated to see how its done there.
|
|
||||||
|
|
||||||
const SLAPrint *print = scene.get_print();
|
|
||||||
if (!print) return;
|
|
||||||
|
|
||||||
m_volumes.clear();
|
|
||||||
|
|
||||||
for (const SLAPrintObject *po : print->objects()) {
|
|
||||||
const ModelObject *mo = po->model_object();
|
|
||||||
TriangleMesh msh = mo->raw_mesh();
|
|
||||||
|
|
||||||
sla::DrainHoles holedata = mo->sla_drain_holes;
|
|
||||||
|
|
||||||
for (const ModelInstance *mi : mo->instances) {
|
|
||||||
|
|
||||||
TriangleMesh mshinst = msh;
|
|
||||||
auto interior = po->hollowed_interior_mesh();
|
|
||||||
interior.transform(po->trafo().inverse());
|
|
||||||
|
|
||||||
mshinst.merge(interior);
|
|
||||||
|
|
||||||
mi->transform_mesh(&mshinst);
|
|
||||||
|
|
||||||
auto bb = mshinst.bounding_box();
|
|
||||||
auto center = bb.center().cast<float>();
|
|
||||||
mshinst.translate(-center);
|
|
||||||
|
|
||||||
add_mesh(mshinst);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const sla::DrainHole &holept : holedata)
|
|
||||||
add_mesh(sla::to_triangle_mesh(holept.to_mesh()));
|
|
||||||
}
|
|
||||||
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
}} // namespace Slic3r::GL
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#ifndef SHADERCSGDISPLAY_HPP
|
|
||||||
#define SHADERCSGDISPLAY_HPP
|
|
||||||
|
|
||||||
#include "Engine.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r { namespace GL {
|
|
||||||
|
|
||||||
class CSGVolume: public Volume
|
|
||||||
{
|
|
||||||
// Extend...
|
|
||||||
};
|
|
||||||
|
|
||||||
class ShaderCSGDisplay: public Display {
|
|
||||||
protected:
|
|
||||||
vector<std::shared_ptr<CSGVolume>> m_volumes;
|
|
||||||
|
|
||||||
void add_mesh(const TriangleMesh &mesh);
|
|
||||||
public:
|
|
||||||
|
|
||||||
void render_scene() override;
|
|
||||||
|
|
||||||
void on_scene_updated(const Scene &scene) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
||||||
#endif // SHADERCSGDISPLAY_HPP
|
|
||||||
@@ -1,734 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <utility>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "Engine.hpp"
|
|
||||||
#include "ShaderCSGDisplay.hpp"
|
|
||||||
|
|
||||||
#include <GL/glew.h>
|
|
||||||
|
|
||||||
#include <opencsg/opencsg.h>
|
|
||||||
// For compilers that support precompilation, includes "wx/wx.h".
|
|
||||||
#include <wx/wxprec.h>
|
|
||||||
#ifndef WX_PRECOMP
|
|
||||||
#include <wx/wx.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <wx/slider.h>
|
|
||||||
#include <wx/tglbtn.h>
|
|
||||||
#include <wx/combobox.h>
|
|
||||||
#include <wx/spinctrl.h>
|
|
||||||
#include <wx/msgdlg.h>
|
|
||||||
#include <wx/glcanvas.h>
|
|
||||||
#include <wx/cmdline.h>
|
|
||||||
|
|
||||||
#include "libslic3r/Model.hpp"
|
|
||||||
#include "libslic3r/Format/3mf.hpp"
|
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
|
||||||
|
|
||||||
#include "slic3r/GUI/Jobs/Job.hpp"
|
|
||||||
#include "slic3r/GUI/ProgressStatusBar.hpp"
|
|
||||||
|
|
||||||
using namespace Slic3r::GL;
|
|
||||||
|
|
||||||
class Renderer {
|
|
||||||
protected:
|
|
||||||
wxGLCanvas *m_canvas;
|
|
||||||
std::shared_ptr<wxGLContext> m_context;
|
|
||||||
public:
|
|
||||||
|
|
||||||
Renderer(wxGLCanvas *c): m_canvas{c} {
|
|
||||||
auto ctx = new wxGLContext(m_canvas);
|
|
||||||
if (!ctx || !ctx->IsOK()) {
|
|
||||||
wxMessageBox("Could not create OpenGL context.", "Error",
|
|
||||||
wxOK | wxICON_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_context.reset(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
wxGLContext * context() { return m_context.get(); }
|
|
||||||
const wxGLContext * context() const { return m_context.get(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
|
||||||
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
|
|
||||||
public:
|
|
||||||
|
|
||||||
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
|
||||||
|
|
||||||
void set_active(long w, long h) override
|
|
||||||
{
|
|
||||||
m_canvas->SetCurrent(*m_context);
|
|
||||||
Slic3r::GL::Display::set_active(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
|
||||||
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
|
|
||||||
public:
|
|
||||||
|
|
||||||
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
|
||||||
|
|
||||||
void set_active(long w, long h) override
|
|
||||||
{
|
|
||||||
m_canvas->SetCurrent(*m_context);
|
|
||||||
Slic3r::GL::Display::set_active(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// The opengl rendering facility. Here we implement the rendering objects.
|
|
||||||
class Canvas: public wxGLCanvas
|
|
||||||
{
|
|
||||||
// One display is active at a time, the OCSGRenderer by default.
|
|
||||||
std::shared_ptr<Slic3r::GL::Display> m_display;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
template<class...Args>
|
|
||||||
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
|
|
||||||
|
|
||||||
std::shared_ptr<Slic3r::GL::Display> get_display() const { return m_display; }
|
|
||||||
|
|
||||||
void set_display(std::shared_ptr<Slic3r::GL::Display> d) { m_display = d; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enumerate possible mouse events, we will record them.
|
|
||||||
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
|
|
||||||
struct Event
|
|
||||||
{
|
|
||||||
EEvents type;
|
|
||||||
long a, b;
|
|
||||||
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a special mouse input adapter, which can store (record) the received
|
|
||||||
// mouse signals into a file and play back the stored events later.
|
|
||||||
class RecorderMouseInput: public MouseInput {
|
|
||||||
std::vector<Event> m_events;
|
|
||||||
bool m_recording = false, m_playing = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void left_click_down() override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(LCLK_D);
|
|
||||||
if (!m_playing) MouseInput::left_click_down();
|
|
||||||
}
|
|
||||||
void left_click_up() override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(LCLK_U);
|
|
||||||
if (!m_playing) MouseInput::left_click_up();
|
|
||||||
}
|
|
||||||
void right_click_down() override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(RCLK_D);
|
|
||||||
if (!m_playing) MouseInput::right_click_down();
|
|
||||||
}
|
|
||||||
void right_click_up() override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(RCLK_U);
|
|
||||||
if (!m_playing) MouseInput::right_click_up();
|
|
||||||
}
|
|
||||||
void double_click() override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(DDCLK);
|
|
||||||
if (!m_playing) MouseInput::double_click();
|
|
||||||
}
|
|
||||||
void scroll(long v, long d, WheelAxis wa) override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(SCRL, v, d);
|
|
||||||
if (!m_playing) MouseInput::scroll(v, d, wa);
|
|
||||||
}
|
|
||||||
void move_to(long x, long y) override
|
|
||||||
{
|
|
||||||
if (m_recording) m_events.emplace_back(MV, x, y);
|
|
||||||
if (!m_playing) MouseInput::move_to(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void save(std::ostream &stream)
|
|
||||||
{
|
|
||||||
for (const Event &evt : m_events)
|
|
||||||
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void load(std::istream &stream)
|
|
||||||
{
|
|
||||||
m_events.clear();
|
|
||||||
while (stream.good()) {
|
|
||||||
int type; long a, b;
|
|
||||||
stream >> type >> a >> b;
|
|
||||||
m_events.emplace_back(EEvents(type), a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
|
|
||||||
|
|
||||||
void play()
|
|
||||||
{
|
|
||||||
m_playing = true;
|
|
||||||
for (const Event &evt : m_events) {
|
|
||||||
switch (evt.type) {
|
|
||||||
case LCLK_U: MouseInput::left_click_up(); break;
|
|
||||||
case LCLK_D: MouseInput::left_click_down(); break;
|
|
||||||
case RCLK_U: MouseInput::right_click_up(); break;
|
|
||||||
case RCLK_D: MouseInput::right_click_down(); break;
|
|
||||||
case DDCLK: MouseInput::double_click(); break;
|
|
||||||
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
|
|
||||||
case MV: MouseInput::move_to(evt.a, evt.b); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxTheApp->Yield();
|
|
||||||
if (!m_playing)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
m_playing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() { m_playing = false; }
|
|
||||||
bool is_playing() const { return m_playing; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// The top level frame of the application.
|
|
||||||
class MyFrame: public wxFrame
|
|
||||||
{
|
|
||||||
// Instantiate the 3D engine.
|
|
||||||
std::shared_ptr<Scene> m_scene; // Model
|
|
||||||
std::shared_ptr<Canvas> m_canvas; // Views store
|
|
||||||
std::shared_ptr<OCSGRenderer> m_ocsgdisplay; // View
|
|
||||||
std::shared_ptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
|
|
||||||
std::shared_ptr<Controller> m_ctl; // Controller
|
|
||||||
|
|
||||||
// Add a status bar with progress indication.
|
|
||||||
std::shared_ptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
|
|
||||||
|
|
||||||
RecorderMouseInput m_mouse;
|
|
||||||
|
|
||||||
// When loading a Model from 3mf and preparing it, we use a separate thread.
|
|
||||||
class SLAJob: public Slic3r::GUI::Job {
|
|
||||||
MyFrame *m_parent;
|
|
||||||
std::unique_ptr<Slic3r::SLAPrint> m_print;
|
|
||||||
std::string m_fname;
|
|
||||||
|
|
||||||
public:
|
|
||||||
SLAJob(MyFrame *frame, const std::string &fname)
|
|
||||||
: Slic3r::GUI::Job{frame->m_stbar}
|
|
||||||
, m_parent{frame}
|
|
||||||
, m_fname{fname}
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Runs in separate thread
|
|
||||||
void process() override;
|
|
||||||
|
|
||||||
const std::string & get_project_fname() const { return m_fname; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
// Runs in the UI thread.
|
|
||||||
void finalize() override
|
|
||||||
{
|
|
||||||
m_parent->m_scene->set_print(std::move(m_print));
|
|
||||||
m_parent->m_stbar->set_status_text(
|
|
||||||
wxString::Format("Model %s loaded.", m_fname));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<SLAJob> m_ui_job;
|
|
||||||
|
|
||||||
// To keep track of the running average of measured fps values.
|
|
||||||
double m_fps_avg = 0.;
|
|
||||||
|
|
||||||
// We need the record button across methods
|
|
||||||
wxToggleButton *m_record_btn;
|
|
||||||
wxComboBox * m_alg_select;
|
|
||||||
wxComboBox * m_depth_select;
|
|
||||||
wxComboBox * m_optimization_select;
|
|
||||||
wxSpinCtrl * m_convexity_spin;
|
|
||||||
wxToggleButton *m_csg_toggle;
|
|
||||||
wxToggleButton *m_ms_toggle;
|
|
||||||
wxStaticText *m_fpstext;
|
|
||||||
|
|
||||||
CSGSettings m_csg_settings;
|
|
||||||
|
|
||||||
void read_csg_settings(const wxCmdLineParser &parser);
|
|
||||||
|
|
||||||
void set_renderer_algorithm(const wxString &alg);
|
|
||||||
|
|
||||||
void activate_canvas_display();
|
|
||||||
|
|
||||||
public:
|
|
||||||
MyFrame(const wxString & title,
|
|
||||||
const wxPoint & pos,
|
|
||||||
const wxSize & size,
|
|
||||||
const wxCmdLineParser &parser);
|
|
||||||
|
|
||||||
// Grab a 3mf and load (hollow it out) within the UI job.
|
|
||||||
void load_model(const std::string &fname) {
|
|
||||||
m_ui_job = std::make_unique<SLAJob>(this, fname);
|
|
||||||
m_ui_job->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load a previously stored mouse event log and play it back.
|
|
||||||
void play_back_mouse(const std::string &events_fname)
|
|
||||||
{
|
|
||||||
std::fstream stream(events_fname, std::fstream::in);
|
|
||||||
|
|
||||||
if (stream.good()) {
|
|
||||||
std::string model_name;
|
|
||||||
std::getline(stream, model_name);
|
|
||||||
load_model(model_name);
|
|
||||||
|
|
||||||
while (!m_ui_job->is_finalized())
|
|
||||||
wxTheApp->Yield();;
|
|
||||||
|
|
||||||
int w, h;
|
|
||||||
stream >> w >> h;
|
|
||||||
SetSize(w, h);
|
|
||||||
|
|
||||||
m_mouse.load(stream);
|
|
||||||
if (m_record_btn) m_record_btn->Disable();
|
|
||||||
m_mouse.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Canvas * canvas() { return m_canvas.get(); }
|
|
||||||
const Canvas * canvas() const { return m_canvas.get(); }
|
|
||||||
|
|
||||||
// Bind the canvas mouse events to a class implementing MouseInput interface
|
|
||||||
void bind_canvas_events(MouseInput &msinput);
|
|
||||||
|
|
||||||
double get_fps_average() const { return m_fps_avg; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Possible OpenCSG configuration values. Will be used on the command line and
|
|
||||||
// on the UI widgets.
|
|
||||||
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
|
|
||||||
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
|
|
||||||
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
|
|
||||||
|
|
||||||
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
|
|
||||||
{
|
|
||||||
auto it = std::find(v.begin(), v.end(), a.ToStdString());
|
|
||||||
return it - v.begin();
|
|
||||||
};
|
|
||||||
|
|
||||||
class App : public wxApp {
|
|
||||||
MyFrame *m_frame = nullptr;
|
|
||||||
wxString m_fname;
|
|
||||||
public:
|
|
||||||
bool OnInit() override {
|
|
||||||
|
|
||||||
wxCmdLineParser parser(argc, argv);
|
|
||||||
|
|
||||||
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
|
||||||
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
|
||||||
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
|
||||||
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
|
||||||
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
|
|
||||||
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
|
|
||||||
|
|
||||||
parser.Parse();
|
|
||||||
|
|
||||||
bool is_play = parser.Found("play", &m_fname);
|
|
||||||
|
|
||||||
m_frame = new MyFrame("OrcaSlicer OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
|
|
||||||
|
|
||||||
if (is_play) {
|
|
||||||
Bind(wxEVT_IDLE, &App::Play, this);
|
|
||||||
m_frame->Show( true );
|
|
||||||
} else m_frame->Show( true );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Play(wxIdleEvent &) {
|
|
||||||
Unbind(wxEVT_IDLE, &App::Play, this);
|
|
||||||
m_frame->play_back_mouse(m_fname.ToStdString());
|
|
||||||
m_frame->Destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
wxIMPLEMENT_APP(App);
|
|
||||||
|
|
||||||
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
|
|
||||||
{
|
|
||||||
wxString alg;
|
|
||||||
parser.Found("algorithm", &alg);
|
|
||||||
|
|
||||||
wxString depth;
|
|
||||||
parser.Found("depth", &depth);
|
|
||||||
|
|
||||||
wxString opt;
|
|
||||||
parser.Found("optimization", &opt);
|
|
||||||
|
|
||||||
long convexity = 1;
|
|
||||||
parser.Found("convexity", &convexity);
|
|
||||||
|
|
||||||
bool csg_off = parser.Found("disable-csg");
|
|
||||||
|
|
||||||
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
|
|
||||||
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
|
|
||||||
|
|
||||||
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
|
|
||||||
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
|
|
||||||
|
|
||||||
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
|
|
||||||
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
|
|
||||||
|
|
||||||
m_csg_settings.set_convexity(unsigned(convexity));
|
|
||||||
m_csg_settings.enable_csg(!csg_off);
|
|
||||||
|
|
||||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyFrame::set_renderer_algorithm(const wxString &alg)
|
|
||||||
{
|
|
||||||
long alg_idx = get_idx(alg, CSG_ALGS);
|
|
||||||
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
|
|
||||||
|
|
||||||
// If there is a valid display in place, save its camera.
|
|
||||||
auto cam = m_canvas->get_display() ?
|
|
||||||
m_canvas->get_display()->get_camera() : nullptr;
|
|
||||||
|
|
||||||
if (alg == "EnricoShader") {
|
|
||||||
m_alg_select->SetSelection(int(alg_idx));
|
|
||||||
m_depth_select->Disable();
|
|
||||||
m_optimization_select->Disable();
|
|
||||||
m_csg_toggle->Disable();
|
|
||||||
|
|
||||||
m_ocsgdisplay.reset();
|
|
||||||
canvas()->set_display(nullptr);
|
|
||||||
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
|
|
||||||
canvas()->set_display(m_shadercsg_display);
|
|
||||||
} else {
|
|
||||||
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
|
|
||||||
m_alg_select->SetSelection(m_csg_settings.get_algo());
|
|
||||||
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
|
|
||||||
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
|
|
||||||
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
|
|
||||||
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
|
|
||||||
m_optimization_select->Enable();
|
|
||||||
m_csg_toggle->Enable();
|
|
||||||
|
|
||||||
m_shadercsg_display.reset();
|
|
||||||
canvas()->set_display(nullptr);
|
|
||||||
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
|
|
||||||
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
|
||||||
canvas()->set_display(m_ocsgdisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cam)
|
|
||||||
m_canvas->get_display()->set_camera(cam);
|
|
||||||
|
|
||||||
m_ctl->remove_displays();
|
|
||||||
m_ctl->add_display(m_canvas->get_display());
|
|
||||||
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
|
|
||||||
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
|
|
||||||
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (IsShown()) {
|
|
||||||
activate_canvas_display();
|
|
||||||
m_canvas->get_display()->on_scene_updated(*m_scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyFrame::activate_canvas_display()
|
|
||||||
{
|
|
||||||
const wxSize ClientSize = m_canvas->GetClientSize();
|
|
||||||
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
|
|
||||||
enable_multisampling(m_ms_toggle->GetValue());
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
|
|
||||||
// This is required even though dc is not used otherwise.
|
|
||||||
wxPaintDC dc(m_canvas.get());
|
|
||||||
const wxSize csize = m_canvas->GetClientSize();
|
|
||||||
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
|
||||||
m_canvas->get_display()->repaint();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
|
|
||||||
const wxSize csize = m_canvas->GetClientSize();
|
|
||||||
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
|
||||||
m_canvas->get_display()->repaint();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do the repaint continuously
|
|
||||||
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
|
|
||||||
m_canvas->get_display()->repaint();
|
|
||||||
evt.RequestMore();
|
|
||||||
});
|
|
||||||
|
|
||||||
bind_canvas_events(m_mouse);
|
|
||||||
}
|
|
||||||
|
|
||||||
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
|
|
||||||
const wxCmdLineParser &parser):
|
|
||||||
wxFrame(nullptr, wxID_ANY, title, pos, size)
|
|
||||||
{
|
|
||||||
wxMenu *menuFile = new wxMenu;
|
|
||||||
menuFile->Append(wxID_OPEN);
|
|
||||||
menuFile->Append(wxID_EXIT);
|
|
||||||
wxMenuBar *menuBar = new wxMenuBar;
|
|
||||||
menuBar->Append( menuFile, "&File" );
|
|
||||||
SetMenuBar( menuBar );
|
|
||||||
|
|
||||||
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
|
|
||||||
m_stbar->embed(this);
|
|
||||||
|
|
||||||
SetStatusText( "Welcome to wxWidgets!" );
|
|
||||||
|
|
||||||
int attribList[] =
|
|
||||||
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
|
|
||||||
// RGB channels each should be allocated with 8 bit depth. One
|
|
||||||
// should almost certainly get these bit depths by default.
|
|
||||||
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
|
|
||||||
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
|
|
||||||
// drivers would most likely work with some alpha plane, but
|
|
||||||
// glReadPixels would not return the alpha channel on NVIDIA if
|
|
||||||
// not requested when the GL context is created.
|
|
||||||
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
|
|
||||||
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
|
|
||||||
|
|
||||||
m_scene = std::make_shared<Scene>();
|
|
||||||
m_ctl = std::make_shared<Controller>();
|
|
||||||
m_ctl->set_scene(m_scene);
|
|
||||||
|
|
||||||
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
|
|
||||||
wxDefaultPosition, wxDefaultSize,
|
|
||||||
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
|
|
||||||
|
|
||||||
read_csg_settings(parser);
|
|
||||||
|
|
||||||
wxPanel *control_panel = new wxPanel(this);
|
|
||||||
|
|
||||||
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
|
|
||||||
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
|
|
||||||
auto console_sizer = new wxBoxSizer(wxVERTICAL);
|
|
||||||
|
|
||||||
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
|
|
||||||
wxDefaultPosition, wxDefaultSize,
|
|
||||||
wxSL_VERTICAL);
|
|
||||||
slider_sizer->Add(slider, 1, wxEXPAND);
|
|
||||||
|
|
||||||
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
|
|
||||||
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
|
|
||||||
|
|
||||||
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
|
|
||||||
m_csg_toggle->SetValue(true);
|
|
||||||
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
|
|
||||||
|
|
||||||
auto add_combobox = [control_panel, console_sizer]
|
|
||||||
(const wxString &label, const std::vector<wxString> &list)
|
|
||||||
{
|
|
||||||
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
|
|
||||||
wxDefaultPosition, wxDefaultSize,
|
|
||||||
int(list.size()), list.data());
|
|
||||||
|
|
||||||
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
|
||||||
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
|
||||||
wxALL | wxALIGN_CENTER, 5);
|
|
||||||
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
|
||||||
console_sizer->Add(sz, 0, wxEXPAND);
|
|
||||||
return widget;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto add_spinctl = [control_panel, console_sizer]
|
|
||||||
(const wxString &label, int initial, int min, int max)
|
|
||||||
{
|
|
||||||
auto widget = new wxSpinCtrl(
|
|
||||||
control_panel, wxID_ANY,
|
|
||||||
wxString::Format("%d", initial),
|
|
||||||
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
|
|
||||||
initial);
|
|
||||||
|
|
||||||
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
|
||||||
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
|
||||||
wxALL | wxALIGN_CENTER, 5);
|
|
||||||
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
|
||||||
console_sizer->Add(sz, 0, wxEXPAND);
|
|
||||||
return widget;
|
|
||||||
};
|
|
||||||
|
|
||||||
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
|
|
||||||
|
|
||||||
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
|
|
||||||
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
|
|
||||||
m_optimization_select = add_combobox("Optimization", CSG_OPT);
|
|
||||||
|
|
||||||
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
|
|
||||||
console_sizer->Add(m_fpstext, 0, wxALL, 5);
|
|
||||||
|
|
||||||
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
|
|
||||||
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
|
|
||||||
|
|
||||||
controlsizer->Add(slider_sizer, 0, wxEXPAND);
|
|
||||||
controlsizer->Add(console_sizer, 1, wxEXPAND);
|
|
||||||
|
|
||||||
control_panel->SetSizer(controlsizer);
|
|
||||||
|
|
||||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
||||||
sizer->Add(m_canvas.get(), 1, wxEXPAND);
|
|
||||||
sizer->Add(control_panel, 0, wxEXPAND);
|
|
||||||
SetSizer(sizer);
|
|
||||||
|
|
||||||
wxString alg;
|
|
||||||
if (!parser.Found("algorithm", &alg)) alg = "Auto";
|
|
||||||
|
|
||||||
set_renderer_algorithm(alg);
|
|
||||||
|
|
||||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
|
|
||||||
if (m_canvas) RemoveChild(m_canvas.get());
|
|
||||||
m_canvas.reset();
|
|
||||||
if (!m_mouse.is_playing()) evt.Skip();
|
|
||||||
else m_mouse.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
|
|
||||||
wxFileDialog dlg(this, "Select project file", wxEmptyString,
|
|
||||||
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
|
|
||||||
|
|
||||||
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
|
|
||||||
}, wxID_OPEN);
|
|
||||||
|
|
||||||
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
|
|
||||||
|
|
||||||
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
|
|
||||||
activate_canvas_display();
|
|
||||||
});
|
|
||||||
|
|
||||||
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
|
|
||||||
m_ctl->move_clip_plane(double(slider->GetValue()));
|
|
||||||
});
|
|
||||||
|
|
||||||
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
|
||||||
enable_multisampling(m_ms_toggle->GetValue());
|
|
||||||
m_canvas->get_display()->repaint();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
|
||||||
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
|
|
||||||
stt.enable_csg(m_csg_toggle->GetValue());
|
|
||||||
m_ocsgdisplay->apply_csgsettings(stt);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
|
||||||
wxString alg = m_alg_select->GetValue();
|
|
||||||
int sel = m_alg_select->GetSelection();
|
|
||||||
m_csg_settings.set_algo(sel);
|
|
||||||
set_renderer_algorithm(alg);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
|
||||||
int sel = m_depth_select->GetSelection();
|
|
||||||
m_csg_settings.set_depth_algo(sel);
|
|
||||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
|
||||||
int sel = m_optimization_select->GetSelection();
|
|
||||||
m_csg_settings.set_optimization(sel);
|
|
||||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
|
|
||||||
int c = m_convexity_spin->GetValue();
|
|
||||||
if (c > 0) {
|
|
||||||
m_csg_settings.set_convexity(unsigned(c));
|
|
||||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
|
|
||||||
if (!m_ui_job) {
|
|
||||||
m_stbar->set_status_text("No project loaded!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_record_btn->GetValue()) {
|
|
||||||
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
|
|
||||||
m_ctl->on_scene_updated(*m_scene);
|
|
||||||
m_mouse.record(true);
|
|
||||||
} else {
|
|
||||||
m_mouse.record(false);
|
|
||||||
wxFileDialog dlg(this, "Select output file",
|
|
||||||
wxEmptyString, wxEmptyString, "*.events",
|
|
||||||
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
|
|
||||||
|
|
||||||
if (dlg.ShowModal() == wxID_OK) {
|
|
||||||
std::fstream stream(dlg.GetPath().ToStdString(),
|
|
||||||
std::fstream::out);
|
|
||||||
|
|
||||||
if (stream.good()) {
|
|
||||||
stream << m_ui_job->get_project_fname() << "\n";
|
|
||||||
wxSize winsize = GetSize();
|
|
||||||
stream << winsize.x << " " << winsize.y << "\n";
|
|
||||||
m_mouse.save(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyFrame::bind_canvas_events(MouseInput &ms)
|
|
||||||
{
|
|
||||||
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
|
|
||||||
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
|
|
||||||
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
|
|
||||||
Slic3r::GL::MouseInput::waVertical :
|
|
||||||
Slic3r::GL::MouseInput::waHorizontal);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
|
|
||||||
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
|
||||||
ms.right_click_down();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
|
||||||
ms.right_click_up();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
|
||||||
ms.left_click_down();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
|
||||||
ms.left_click_up();
|
|
||||||
});
|
|
||||||
|
|
||||||
ms.add_listener(m_ctl);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyFrame::SLAJob::process()
|
|
||||||
{
|
|
||||||
using SlStatus = Slic3r::PrintBase::SlicingStatus;
|
|
||||||
|
|
||||||
Slic3r::DynamicPrintConfig cfg;
|
|
||||||
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
|
|
||||||
|
|
||||||
m_print = std::make_unique<Slic3r::SLAPrint>();
|
|
||||||
m_print->apply(model, cfg);
|
|
||||||
|
|
||||||
Slic3r::PrintBase::TaskParams params;
|
|
||||||
params.to_object_step = Slic3r::slaposHollowing;
|
|
||||||
m_print->set_task(params);
|
|
||||||
|
|
||||||
m_print->set_status_callback([this](const SlStatus &status) {
|
|
||||||
update_status(status.percent, status.text);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
m_print->process();
|
|
||||||
} catch(std::exception &e) {
|
|
||||||
update_status(0, wxString("Exception during processing: ") + e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//int main() {}
|
|
||||||
+1
-5
@@ -23,15 +23,11 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libcairo2-dev \
|
libcairo2-dev \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
libglew-dev \
|
|
||||||
libglu1-mesa-dev \
|
|
||||||
libglu1-mesa-dev \
|
|
||||||
libgstreamer1.0-dev \
|
libgstreamer1.0-dev \
|
||||||
libgstreamerd-3-dev \
|
libgstreamerd-3-dev \
|
||||||
libgstreamer-plugins-base1.0-dev \
|
libgstreamer-plugins-base1.0-dev \
|
||||||
libgstreamer-plugins-good1.0-dev \
|
libgstreamer-plugins-good1.0-dev \
|
||||||
libgtk-3-dev \
|
libgtk-3-dev \
|
||||||
libgtk-3-dev \
|
|
||||||
libsecret-1-dev \
|
libsecret-1-dev \
|
||||||
libsoup2.4-dev \
|
libsoup2.4-dev \
|
||||||
libssl3 \
|
libssl3 \
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libcairo2-dev \
|
libcairo2-dev \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
libglew-dev \
|
|
||||||
libglu1-mesa-dev \
|
|
||||||
libgstreamer1.0-dev \
|
libgstreamer1.0-dev \
|
||||||
libgstreamerd-3-dev \
|
libgstreamerd-3-dev \
|
||||||
libgstreamer-plugins-base1.0-dev \
|
libgstreamer-plugins-base1.0-dev \
|
||||||
|
|||||||
@@ -55,21 +55,6 @@ modules:
|
|||||||
url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz
|
url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz
|
||||||
sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39
|
sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39
|
||||||
|
|
||||||
- name: glu
|
|
||||||
build-options:
|
|
||||||
cxxflags: -Wno-register
|
|
||||||
config-opts:
|
|
||||||
- --disable-static
|
|
||||||
sources:
|
|
||||||
- type: archive
|
|
||||||
url: https://ftp.osuosl.org/pub/blfs/conglomeration/glu/glu-9.0.2.tar.xz
|
|
||||||
sha256: 6e7280ff585c6a1d9dfcdf2fca489251634b3377bfc33c29e4002466a38d02d4
|
|
||||||
cleanup:
|
|
||||||
- /include
|
|
||||||
- /lib/*.a
|
|
||||||
- /lib/*.la
|
|
||||||
- /lib/pkgconfig
|
|
||||||
|
|
||||||
- name: kde-extra-cmake-modules
|
- name: kde-extra-cmake-modules
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
sources:
|
sources:
|
||||||
@@ -217,12 +202,6 @@ modules:
|
|||||||
sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a
|
sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a
|
||||||
dest: external-packages/GLFW
|
dest: external-packages/GLFW
|
||||||
|
|
||||||
# OpenCSG 1.4.2
|
|
||||||
- type: file
|
|
||||||
url: https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
|
|
||||||
sha256: 51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
|
|
||||||
dest: external-packages/OpenCSG
|
|
||||||
|
|
||||||
# SolveSpace libslvs (2D sketch constraint solver, Design tab)
|
# SolveSpace libslvs (2D sketch constraint solver, Design tab)
|
||||||
- type: file
|
- type: file
|
||||||
url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip
|
url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export REQUIRED_DEV_PACKAGES=(
|
|||||||
file
|
file
|
||||||
gettext
|
gettext
|
||||||
git
|
git
|
||||||
glew
|
|
||||||
gst-plugins-good
|
gst-plugins-good
|
||||||
gstreamer
|
gstreamer
|
||||||
gtk3
|
gtk3
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export REQUIRED_DEV_PACKAGES=(
|
|||||||
file
|
file
|
||||||
gettext
|
gettext
|
||||||
git
|
git
|
||||||
glew
|
|
||||||
gst-plugins-good
|
gst-plugins-good
|
||||||
gstreamer
|
gstreamer
|
||||||
gtk3
|
gtk3
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ export REQUIRED_BUNDLES=(
|
|||||||
c-basic
|
c-basic
|
||||||
dev-utils
|
dev-utils
|
||||||
devpkg-curl
|
devpkg-curl
|
||||||
devpkg-glew
|
|
||||||
devpkg-glu
|
|
||||||
devpkg-gstreamer
|
devpkg-gstreamer
|
||||||
devpkg-gtk3
|
devpkg-gtk3
|
||||||
|
devpkg-libglvnd
|
||||||
devpkg-libmspack
|
devpkg-libmspack
|
||||||
devpkg-libsecret
|
devpkg-libsecret
|
||||||
devpkg-openssl
|
devpkg-openssl
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ REQUIRED_DEV_PACKAGES=(
|
|||||||
gstreamer1.0-gtk3
|
gstreamer1.0-gtk3
|
||||||
libcurl4-openssl-dev
|
libcurl4-openssl-dev
|
||||||
libdbus-1-dev
|
libdbus-1-dev
|
||||||
libglew-dev
|
libgl-dev
|
||||||
libgstreamerd-3-dev
|
libgstreamerd-3-dev
|
||||||
libgtk-3-dev
|
libgtk-3-dev
|
||||||
libmspack-dev
|
libmspack-dev
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ REQUIRED_DEV_PACKAGES=(
|
|||||||
libspnav-devel
|
libspnav-devel
|
||||||
libtool
|
libtool
|
||||||
m4
|
m4
|
||||||
mesa-libGLU-devel
|
mesa-libGL-devel
|
||||||
ninja-build
|
ninja-build
|
||||||
openssl-devel
|
openssl-devel
|
||||||
perl-FindBin
|
perl-FindBin
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ REQUIRED_DEV_PACKAGES=(
|
|||||||
dev-vcs/git
|
dev-vcs/git
|
||||||
gui-libs/eglexternalplatform
|
gui-libs/eglexternalplatform
|
||||||
kde-frameworks/extra-cmake-modules
|
kde-frameworks/extra-cmake-modules
|
||||||
media-libs/glew
|
|
||||||
media-libs/gst-plugins-base:1.0
|
media-libs/gst-plugins-base:1.0
|
||||||
media-libs/gstreamer:1.0
|
media-libs/gstreamer:1.0
|
||||||
media-plugins/gst-plugins-gtk:1.0
|
media-plugins/gst-plugins-gtk:1.0
|
||||||
@@ -31,6 +30,7 @@ REQUIRED_DEV_PACKAGES=(
|
|||||||
sys-devel/gettext
|
sys-devel/gettext
|
||||||
sys-devel/m4
|
sys-devel/m4
|
||||||
virtual/libudev
|
virtual/libudev
|
||||||
|
virtual/opengl
|
||||||
x11-libs/gtk+:3
|
x11-libs/gtk+:3
|
||||||
dev-util/pkgconf
|
dev-util/pkgconf
|
||||||
dev-lang/yasm
|
dev-lang/yasm
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ REQUIRED_DEV_PACKAGES=(
|
|||||||
libspnav-devel
|
libspnav-devel
|
||||||
libtool
|
libtool
|
||||||
m4
|
m4
|
||||||
glu-devel
|
Mesa-libGL-devel
|
||||||
ninja-build
|
ninja-build
|
||||||
openssl-devel
|
openssl-devel
|
||||||
perl-FindBin-Real
|
perl-FindBin-Real
|
||||||
|
|||||||
@@ -189,7 +189,6 @@ else ()
|
|||||||
target_link_libraries(OrcaSlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads pangoft2-1.0)
|
target_link_libraries(OrcaSlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads pangoft2-1.0)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
|
|
||||||
if (SLIC3R_GUI)
|
if (SLIC3R_GUI)
|
||||||
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
|
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
|
||||||
target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector)
|
target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector)
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ TARGET_BIN="\$1"
|
|||||||
|
|
||||||
if target_missing_runtime_library "\$TARGET_BIN" "libOpenGL.so.0" || ! has_host_runtime_library "libOpenGL.so.0"; then
|
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 "Error: missing host OpenGL runtime library libOpenGL.so.0." >&2
|
||||||
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0 and libglu1-mesa" >&2
|
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0" >&2
|
||||||
echo "On Arch/CachyOS, install: libglvnd" >&2
|
echo "On Arch/CachyOS, install: libglvnd" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -411,6 +411,9 @@ void AppConfig::set_defaults()
|
|||||||
if (get("show_overhang").empty())
|
if (get("show_overhang").empty())
|
||||||
set_bool("show_overhang", false);
|
set_bool("show_overhang", false);
|
||||||
|
|
||||||
|
if (get("show_center_of_mass").empty())
|
||||||
|
set_bool("show_center_of_mass", false);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
//#ifdef SUPPORT_3D_CONNEXION
|
//#ifdef SUPPORT_3D_CONNEXION
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ set(lisbslic3r_sources
|
|||||||
CommonDefs.hpp
|
CommonDefs.hpp
|
||||||
Config.cpp
|
Config.cpp
|
||||||
Config.hpp
|
Config.hpp
|
||||||
|
ConnectedBodies.cpp
|
||||||
|
ConnectedBodies.hpp
|
||||||
ContourZ.cpp
|
ContourZ.cpp
|
||||||
CustomGCode.cpp
|
CustomGCode.cpp
|
||||||
CustomGCode.hpp
|
CustomGCode.hpp
|
||||||
@@ -180,6 +182,8 @@ set(lisbslic3r_sources
|
|||||||
Fill/FillPlanePath.hpp
|
Fill/FillPlanePath.hpp
|
||||||
Fill/FillRectilinear.cpp
|
Fill/FillRectilinear.cpp
|
||||||
Fill/FillRectilinear.hpp
|
Fill/FillRectilinear.hpp
|
||||||
|
Fill/FillTpmsAdaptive.cpp
|
||||||
|
Fill/FillTpmsAdaptive.hpp
|
||||||
Fill/FillTpmsD.cpp
|
Fill/FillTpmsD.cpp
|
||||||
Fill/FillTpmsD.hpp
|
Fill/FillTpmsD.hpp
|
||||||
Fill/FillTpmsFK.cpp
|
Fill/FillTpmsFK.cpp
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Slic3r { namespace csg {
|
|||||||
// A CSGPartT should be an object that can provide at least a mesh + trafo and an
|
// 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
|
// associated csg operation. A collection of CSGPartT objects can then
|
||||||
// be interpreted as one model and used in various contexts. It can be assembled
|
// 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
|
// with CGAL or OpenVDB or provided to a ray-tracer to
|
||||||
// deal with various parts of it according to the supported CSG types...
|
// deal with various parts of it according to the supported CSG types...
|
||||||
//
|
//
|
||||||
// A few simple templated interface functions are provided here and a default
|
// A few simple templated interface functions are provided here and a default
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
#include "ConnectedBodies.hpp"
|
||||||
|
|
||||||
|
#include "AABBTreeIndirect.hpp"
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
|
#include "ClipperUtils.hpp"
|
||||||
|
#include "ExPolygon.hpp"
|
||||||
|
#include "Geometry/ConvexHull.hpp"
|
||||||
|
#include "Point.hpp"
|
||||||
|
#include "Polygon.hpp"
|
||||||
|
#include "TriangleMesh.hpp"
|
||||||
|
#include "TriangleMeshSlicer.hpp"
|
||||||
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
#include <tbb/blocked_range.h>
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
std::vector<std::vector<size_t>> connected_bodies(const std::vector<const ExPolygons *> &layers, size_t &count,
|
||||||
|
const std::function<void()> &throw_if_canceled)
|
||||||
|
{
|
||||||
|
// Union-find over the islands of all layers, numbered layer after layer.
|
||||||
|
std::vector<size_t> first(layers.size() + 1, 0);
|
||||||
|
for (size_t l = 0; l < layers.size(); ++l)
|
||||||
|
first[l + 1] = first[l] + layers[l]->size();
|
||||||
|
std::vector<size_t> parent(first.back());
|
||||||
|
for (size_t i = 0; i < parent.size(); ++i)
|
||||||
|
parent[i] = i;
|
||||||
|
const auto find = [&parent](size_t i) {
|
||||||
|
while (parent[i] != i)
|
||||||
|
i = parent[i] = parent[parent[i]];
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::vector<BoundingBox>> boxes(layers.size());
|
||||||
|
for (size_t l = 0; l < layers.size(); ++l)
|
||||||
|
for (const ExPolygon &island : *layers[l])
|
||||||
|
boxes[l].emplace_back(get_extents(island));
|
||||||
|
for (size_t l = 0; l + 1 < layers.size(); ++l) {
|
||||||
|
if (throw_if_canceled)
|
||||||
|
throw_if_canceled();
|
||||||
|
// Index the smaller of the two layers, so that a fragmented layer is not scanned island by island.
|
||||||
|
size_t a_layer = l;
|
||||||
|
size_t b_layer = l + 1;
|
||||||
|
if (layers[a_layer]->size() < layers[b_layer]->size())
|
||||||
|
std::swap(a_layer, b_layer);
|
||||||
|
if (layers[b_layer]->empty())
|
||||||
|
continue;
|
||||||
|
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
|
||||||
|
std::vector<AABBTreeIndirect::BoundingBoxWrapper> wrappers;
|
||||||
|
wrappers.reserve(boxes[b_layer].size());
|
||||||
|
for (size_t b = 0; b < boxes[b_layer].size(); ++b)
|
||||||
|
wrappers.emplace_back(b, boxes[b_layer][b]);
|
||||||
|
IslandTree tree;
|
||||||
|
tree.build_modify_input(wrappers);
|
||||||
|
for (size_t a = 0; a < boxes[a_layer].size(); ++a) {
|
||||||
|
const IslandTree::BoundingBox query(boxes[a_layer][a].min, boxes[a_layer][a].max);
|
||||||
|
AABBTreeIndirect::traverse(
|
||||||
|
tree, [&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
|
||||||
|
[&](const IslandTree::Node &node) {
|
||||||
|
// The tree's boxes are widened by an epsilon, and islands already joined need no clipping.
|
||||||
|
const size_t b = node.idx;
|
||||||
|
if (boxes[a_layer][a].overlap(boxes[b_layer][b]) && find(first[a_layer] + a) != find(first[b_layer] + b) &&
|
||||||
|
!intersection_ex((*layers[a_layer])[a], (*layers[b_layer])[b]).empty())
|
||||||
|
parent[find(first[a_layer] + a)] = find(first[b_layer] + b);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> body(parent.size(), std::numeric_limits<size_t>::max());
|
||||||
|
std::vector<std::vector<size_t>> out(layers.size());
|
||||||
|
count = 0;
|
||||||
|
for (size_t l = 0; l < layers.size(); ++l)
|
||||||
|
for (size_t i = 0; i < layers[l]->size(); ++i) {
|
||||||
|
size_t &b = body[find(first[l] + i)];
|
||||||
|
if (b == std::numeric_limits<size_t>::max())
|
||||||
|
b = count++;
|
||||||
|
out[l].emplace_back(b);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
IslandLocator::IslandLocator(const ExPolygons &islands, coord_t margin) : m_islands(&islands), m_alone(islands.size(), true)
|
||||||
|
{
|
||||||
|
m_boxes.reserve(islands.size());
|
||||||
|
for (const ExPolygon &island : islands)
|
||||||
|
m_boxes.emplace_back(get_extents(island).inflated(margin));
|
||||||
|
// Sweep the boxes along x, so that only those reaching each other are compared.
|
||||||
|
std::vector<size_t> order(m_boxes.size());
|
||||||
|
for (size_t i = 0; i < order.size(); ++i)
|
||||||
|
order[i] = i;
|
||||||
|
std::sort(order.begin(), order.end(), [this](size_t l, size_t r) { return m_boxes[l].min.x() < m_boxes[r].min.x(); });
|
||||||
|
for (size_t a = 0; a < order.size(); ++a)
|
||||||
|
for (size_t b = a + 1; b < order.size() && m_boxes[order[b]].min.x() <= m_boxes[order[a]].max.x(); ++b)
|
||||||
|
if (m_boxes[order[a]].overlap(m_boxes[order[b]]))
|
||||||
|
m_alone[order[a]] = m_alone[order[b]] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IslandLocator::holds(size_t island, const Point &point, bool strict) const
|
||||||
|
{
|
||||||
|
return m_boxes[island].contains(point) && ((m_alone[island] && !strict) || (*m_islands)[island].contains(point));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, double> IslandLocator::find(const Point &point, bool strict) const
|
||||||
|
{
|
||||||
|
int nearest = -1;
|
||||||
|
double distance = std::numeric_limits<double>::max();
|
||||||
|
for (size_t i = 0; i < m_boxes.size(); ++i)
|
||||||
|
if (m_boxes[i].contains(point)) {
|
||||||
|
if ((m_alone[i] && !strict) || (*m_islands)[i].contains(point))
|
||||||
|
return { int(i), 0. };
|
||||||
|
if (const double d = ((*m_islands)[i].point_projection(point) - point).cast<double>().squaredNorm(); d < distance) {
|
||||||
|
distance = d;
|
||||||
|
nearest = int(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { nearest, distance };
|
||||||
|
}
|
||||||
|
|
||||||
|
// The area of polygons and their first and second moments of area, which holes, running clockwise, subtract.
|
||||||
|
struct AreaMoments
|
||||||
|
{
|
||||||
|
double area{ 0. };
|
||||||
|
Vec2d first{ Vec2d::Zero() };
|
||||||
|
// Of x^2, y^2 and xy.
|
||||||
|
Vec3d second{ Vec3d::Zero() };
|
||||||
|
|
||||||
|
void add(const Polygon &polygon)
|
||||||
|
{
|
||||||
|
if (polygon.points.size() < 3)
|
||||||
|
return;
|
||||||
|
Vec2d p1 = unscaled(polygon.points.back());
|
||||||
|
for (const Point &point : polygon.points) {
|
||||||
|
const Vec2d p2 = unscaled(point);
|
||||||
|
const double a = cross2(p1, p2);
|
||||||
|
area += a / 2.;
|
||||||
|
first += a / 6. * (p1 + p2);
|
||||||
|
second += a / 12. *
|
||||||
|
Vec3d(p1.x() * p1.x() + p1.x() * p2.x() + p2.x() * p2.x(), p1.y() * p1.y() + p1.y() * p2.y() + p2.y() * p2.y(),
|
||||||
|
p1.x() * p1.y() + p2.x() * p2.y() + 0.5 * (p1.x() * p2.y() + p2.x() * p1.y()));
|
||||||
|
p1 = p2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mass, volume and the first and second moments of mass about the origin.
|
||||||
|
struct Moments
|
||||||
|
{
|
||||||
|
double mass{ 0. };
|
||||||
|
double volume{ 0. };
|
||||||
|
Vec3d first{ Vec3d::Zero() };
|
||||||
|
Matrix3d second{ Matrix3d::Zero() };
|
||||||
|
|
||||||
|
void add(const Moments &other)
|
||||||
|
{
|
||||||
|
mass += other.mass;
|
||||||
|
volume += other.volume;
|
||||||
|
first += other.first;
|
||||||
|
second += other.second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BoundingBoxf3 SolidBody::bounding_box(const Transform3d &trafo) const
|
||||||
|
{
|
||||||
|
BoundingBoxf3 box;
|
||||||
|
for (const Point &point : hull.points)
|
||||||
|
for (const double z : { z_min, z_max })
|
||||||
|
box.merge(trafo * Vec3d(unscaled(point.x()), unscaled(point.y()), z));
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SolidBody> solid_bodies(const std::vector<MeshInPlace> &solids, const std::vector<double> &densities,
|
||||||
|
const std::vector<MeshInPlace> &negatives, size_t slabs)
|
||||||
|
{
|
||||||
|
assert(densities.size() == solids.size());
|
||||||
|
double z_min = std::numeric_limits<double>::max();
|
||||||
|
double z_max = std::numeric_limits<double>::lowest();
|
||||||
|
for (const auto &[mesh, trafo] : solids)
|
||||||
|
for (const stl_vertex &v : mesh->vertices) {
|
||||||
|
const double z = (trafo * v.cast<double>()).z();
|
||||||
|
z_min = std::min(z_min, z);
|
||||||
|
z_max = std::max(z_max, z);
|
||||||
|
}
|
||||||
|
if (z_min >= z_max || slabs == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Each slab sliced at its middle.
|
||||||
|
const double thickness = (z_max - z_min) / double(slabs);
|
||||||
|
std::vector<float> zs(slabs);
|
||||||
|
for (size_t k = 0; k < slabs; ++k)
|
||||||
|
zs[k] = float(z_min + (double(k) + 0.5) * thickness);
|
||||||
|
|
||||||
|
MeshSlicingParamsEx params;
|
||||||
|
const auto slice = [&zs, ¶ms](const MeshInPlace &mesh) {
|
||||||
|
params.trafo = mesh.second;
|
||||||
|
return slice_mesh_ex(*mesh.first, zs, params);
|
||||||
|
};
|
||||||
|
std::vector<std::vector<ExPolygons>> slices;
|
||||||
|
for (const MeshInPlace &solid : solids)
|
||||||
|
slices.emplace_back(slice(solid));
|
||||||
|
std::vector<ExPolygons> cut(slabs);
|
||||||
|
for (const MeshInPlace &negative : negatives) {
|
||||||
|
std::vector<ExPolygons> slices_negative = slice(negative);
|
||||||
|
for (size_t k = 0; k < slabs; ++k)
|
||||||
|
append(cut[k], std::move(slices_negative[k]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The islands of each slab, and the moments of what each solid prints of them with its density.
|
||||||
|
const bool uniform = std::all_of(densities.begin(), densities.end(), [&densities](double d) { return d == densities.front(); });
|
||||||
|
std::vector<ExPolygons> islands(slabs);
|
||||||
|
std::vector<std::vector<Moments>> moments(slabs);
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, slabs), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t k = range.begin(); k < range.end(); ++k) {
|
||||||
|
ExPolygons all;
|
||||||
|
for (const std::vector<ExPolygons> &solid : slices)
|
||||||
|
append(all, solid[k]);
|
||||||
|
islands[k] = diff_ex(union_ex(all), cut[k]);
|
||||||
|
moments[k].assign(islands[k].size(), {});
|
||||||
|
const double z = zs[k];
|
||||||
|
const auto add = [&](const ExPolygon ®ion, double density, size_t island) {
|
||||||
|
AreaMoments area;
|
||||||
|
area.add(region.contour);
|
||||||
|
for (const Polygon &hole : region.holes)
|
||||||
|
area.add(hole);
|
||||||
|
if (area.area <= 0.)
|
||||||
|
return;
|
||||||
|
// A prism of the slab's thickness.
|
||||||
|
Matrix3d second;
|
||||||
|
second << area.second.x(), area.second.z(), area.first.x() * z, area.second.z(), area.second.y(), area.first.y() * z,
|
||||||
|
area.first.x() * z, area.first.y() * z, area.area * (z * z + thickness * thickness / 12.);
|
||||||
|
moments[k][island].add({ density * area.area * thickness, area.area * thickness,
|
||||||
|
density * thickness * Vec3d(area.first.x(), area.first.y(), area.area * z), density * thickness * second });
|
||||||
|
};
|
||||||
|
if (uniform) {
|
||||||
|
for (size_t j = 0; j < islands[k].size(); ++j)
|
||||||
|
add(islands[k][j], densities.front(), j);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// A later solid prints where it overlaps an earlier one, and each region it prints lies in one island.
|
||||||
|
const IslandLocator locator(islands[k], 10);
|
||||||
|
ExPolygons later = cut[k];
|
||||||
|
for (size_t i = solids.size(); i-- > 0;)
|
||||||
|
if (!slices[i][k].empty()) {
|
||||||
|
for (const ExPolygon ®ion : diff_ex(slices[i][k], later))
|
||||||
|
if (const int island = locator.find(region.contour.points.front()).first; island >= 0)
|
||||||
|
add(region, densities[i], size_t(island));
|
||||||
|
later = union_ex(later, slices[i][k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<const ExPolygons *> layers;
|
||||||
|
layers.reserve(slabs);
|
||||||
|
for (const ExPolygons &layer : islands)
|
||||||
|
layers.emplace_back(&layer);
|
||||||
|
size_t count = 0;
|
||||||
|
const std::vector<std::vector<size_t>> bodies = connected_bodies(layers, count);
|
||||||
|
std::vector<Moments> sums(count);
|
||||||
|
std::vector<Points> outlines(count);
|
||||||
|
std::vector<SolidBody> out(count);
|
||||||
|
for (SolidBody &body : out) {
|
||||||
|
body.z_min = std::numeric_limits<double>::max();
|
||||||
|
body.z_max = std::numeric_limits<double>::lowest();
|
||||||
|
}
|
||||||
|
for (size_t k = 0; k < slabs; ++k)
|
||||||
|
for (size_t j = 0; j < islands[k].size(); ++j) {
|
||||||
|
const size_t body = bodies[k][j];
|
||||||
|
sums[body].add(moments[k][j]);
|
||||||
|
append(outlines[body], islands[k][j].contour.points);
|
||||||
|
out[body].z_min = std::min(out[body].z_min, zs[k] - 0.5 * thickness);
|
||||||
|
out[body].z_max = std::max(out[body].z_max, zs[k] + 0.5 * thickness);
|
||||||
|
}
|
||||||
|
for (size_t body = 0; body < count; ++body)
|
||||||
|
if (const Moments &sum = sums[body]; sum.mass > 0.) {
|
||||||
|
const Vec3d center = sum.first / sum.mass;
|
||||||
|
MassProperties &solid = out[body];
|
||||||
|
solid = { sum.mass, sum.volume, center, sum.second / sum.mass - center * center.transpose() };
|
||||||
|
out[body].hull = Geometry::convex_hull(std::move(outlines[body]));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
|
#include "ExPolygon.hpp"
|
||||||
|
#include "Point.hpp"
|
||||||
|
#include "Polygon.hpp"
|
||||||
|
#include "TriangleMesh.hpp"
|
||||||
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
#include <admesh/stl.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// The connected body of each island of each layer: islands of adjacent layers whose slices overlap are one body.
|
||||||
|
// Bodies are numbered from 0 in the order of their first island.
|
||||||
|
std::vector<std::vector<size_t>> connected_bodies(const std::vector<const ExPolygons *> &layers, size_t &count,
|
||||||
|
const std::function<void()> &throw_if_canceled = nullptr);
|
||||||
|
|
||||||
|
// Finds the island of a layer that a point lies in, testing the polygons only where the boxes of several islands hold it.
|
||||||
|
class IslandLocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// The islands must outlive the locator. Their boxes are widened by the margin, for points reaching past an outline.
|
||||||
|
IslandLocator(const ExPolygons &islands, coord_t margin);
|
||||||
|
// Whether the island holds the point, or its box does where no other box reaches unless strict.
|
||||||
|
bool holds(size_t island, const Point &point, bool strict = false) const;
|
||||||
|
// The island holding the point as above, else the nearest one whose box holds it, with the squared distance to it; -1
|
||||||
|
// for none.
|
||||||
|
std::pair<int, double> find(const Point &point, bool strict = false) const;
|
||||||
|
const std::vector<BoundingBox> &boxes() const { return m_boxes; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const ExPolygons *m_islands;
|
||||||
|
std::vector<BoundingBox> m_boxes;
|
||||||
|
std::vector<bool> m_alone;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MeshInPlace = std::pair<const indexed_triangle_set *, Transform3d>;
|
||||||
|
|
||||||
|
// A connected body of solids, with its outline seen from above as a convex hull and the height it spans.
|
||||||
|
struct SolidBody : MassProperties
|
||||||
|
{
|
||||||
|
Polygon hull;
|
||||||
|
double z_min{ 0. };
|
||||||
|
double z_max{ 0. };
|
||||||
|
|
||||||
|
// Its box once transformed, tight for a transformation that rotates about z only.
|
||||||
|
BoundingBoxf3 bounding_box(const Transform3d &trafo) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Each connected body of the union of the solids less the negatives, sliced into slabs, each solid weighing its density.
|
||||||
|
// Where solids overlap, the later one counts, as slicing prints it.
|
||||||
|
std::vector<SolidBody> solid_bodies(const std::vector<MeshInPlace> &solids, const std::vector<double> &densities,
|
||||||
|
const std::vector<MeshInPlace> &negatives, size_t slabs);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -311,6 +311,11 @@ struct SurfaceFillParams
|
|||||||
// For Gyroid: when true, use the parameterized "optimized" wave.
|
// For Gyroid: when true, use the parameterized "optimized" wave.
|
||||||
bool gyroid_optimized = false;
|
bool gyroid_optimized = false;
|
||||||
|
|
||||||
|
// For TPMS: grade the density from the surface to the interior of the object.
|
||||||
|
TpmsAdaptiveMode tpms_adaptive = TpmsAdaptiveMode::Disabled;
|
||||||
|
float tpms_interior_density = 0.f;
|
||||||
|
TpmsAdaptiveGradient tpms_adaptive_gradient = TpmsAdaptiveGradient::Linear;
|
||||||
|
|
||||||
// Orca: corner smoothing factor in the range [0, 1].
|
// Orca: corner smoothing factor in the range [0, 1].
|
||||||
double smooth_factor { 0. };
|
double smooth_factor { 0. };
|
||||||
|
|
||||||
@@ -353,6 +358,9 @@ struct SurfaceFillParams
|
|||||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
||||||
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||||
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
|
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
|
||||||
|
RETURN_COMPARE_NON_EQUAL(tpms_adaptive);
|
||||||
|
RETURN_COMPARE_NON_EQUAL(tpms_interior_density);
|
||||||
|
RETURN_COMPARE_NON_EQUAL(tpms_adaptive_gradient);
|
||||||
RETURN_COMPARE_NON_EQUAL(smooth_factor);
|
RETURN_COMPARE_NON_EQUAL(smooth_factor);
|
||||||
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
|
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
|
||||||
RETURN_COMPARE_NON_EQUAL(separated_infills);
|
RETURN_COMPARE_NON_EQUAL(separated_infills);
|
||||||
@@ -386,6 +394,9 @@ struct SurfaceFillParams
|
|||||||
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
|
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
|
||||||
this->separated_infills == rhs.separated_infills &&
|
this->separated_infills == rhs.separated_infills &&
|
||||||
this->gyroid_optimized == rhs.gyroid_optimized &&
|
this->gyroid_optimized == rhs.gyroid_optimized &&
|
||||||
|
this->tpms_adaptive == rhs.tpms_adaptive &&
|
||||||
|
this->tpms_interior_density == rhs.tpms_interior_density &&
|
||||||
|
this->tpms_adaptive_gradient == rhs.tpms_adaptive_gradient &&
|
||||||
this->smooth_factor == rhs.smooth_factor &&
|
this->smooth_factor == rhs.smooth_factor &&
|
||||||
this->fill_order == rhs.fill_order;
|
this->fill_order == rhs.fill_order;
|
||||||
}
|
}
|
||||||
@@ -994,15 +1005,23 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
|||||||
// Orca: apply fill multiline only for sparse infill
|
// Orca: apply fill multiline only for sparse infill
|
||||||
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
|
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
|
||||||
|
|
||||||
// Pass through gyroid_optimized only when the effective pattern is Gyroid,
|
// Orca: Pass through separated_infills only where it can move the pattern.
|
||||||
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
|
|
||||||
// (which would unnecessarily split fill batching).
|
|
||||||
// Stored on SurfaceFillParams; copied to FillParams during conversion.
|
|
||||||
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
|
|
||||||
// Orca: Likewise separated_infills only where it can move the pattern.
|
|
||||||
params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) &&
|
params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) &&
|
||||||
params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface;
|
params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface;
|
||||||
|
|
||||||
|
// Orca: only the TPMS sparse infill is graded; reset otherwise, as params is reused.
|
||||||
|
params.tpms_adaptive = is_tpms_adaptive_pattern(params.pattern) && params.extrusion_role == erInternalInfill ?
|
||||||
|
region_config.tpms_adaptive.value : TpmsAdaptiveMode::Disabled;
|
||||||
|
const bool tpms_adaptive = params.tpms_adaptive != TpmsAdaptiveMode::Disabled;
|
||||||
|
params.tpms_interior_density = tpms_adaptive ? std::max(1.f, float(region_config.tpms_interior_density)) : 0.f;
|
||||||
|
params.tpms_adaptive_gradient = tpms_adaptive ? region_config.tpms_adaptive_gradient.value : TpmsAdaptiveGradient::Linear;
|
||||||
|
|
||||||
|
// Pass through gyroid_optimized only when the effective pattern is Gyroid,
|
||||||
|
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
|
||||||
|
// (which would unnecessarily split fill batching). Adaptive density replaces it.
|
||||||
|
// Stored on SurfaceFillParams; copied to FillParams during conversion.
|
||||||
|
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized && !tpms_adaptive;
|
||||||
|
|
||||||
if (params.extrusion_role == erInternalInfill) {
|
if (params.extrusion_role == erInternalInfill) {
|
||||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
|
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
|
||||||
region_config.sparse_infill_rotate_template.value);
|
region_config.sparse_infill_rotate_template.value);
|
||||||
@@ -1354,6 +1373,7 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
|
|||||||
f->angle = surface_fill.params.angle;
|
f->angle = surface_fill.params.angle;
|
||||||
f->fixed_angle = surface_fill.params.fixed_angle;
|
f->fixed_angle = surface_fill.params.fixed_angle;
|
||||||
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
|
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
|
||||||
|
f->tpms_radial_field = this->object()->tpms_radial_field(surface_fill.params.tpms_adaptive);
|
||||||
f->print_config = &this->object()->print()->config();
|
f->print_config = &this->object()->print()->config();
|
||||||
f->print_object_config = &this->object()->config();
|
f->print_object_config = &this->object()->config();
|
||||||
if (surface_fill.params.pattern == ipConcentricInternal) {
|
if (surface_fill.params.pattern == ipConcentricInternal) {
|
||||||
@@ -1403,6 +1423,9 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
|
|||||||
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
|
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
|
||||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||||
|
params.tpms_adaptive = surface_fill.params.tpms_adaptive;
|
||||||
|
params.tpms_interior_density = float(0.01 * surface_fill.params.tpms_interior_density);
|
||||||
|
params.tpms_adaptive_gradient = surface_fill.params.tpms_adaptive_gradient;
|
||||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
@@ -1575,6 +1598,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
|
|||||||
f->angle = surface_fill.params.angle;
|
f->angle = surface_fill.params.angle;
|
||||||
f->fixed_angle = surface_fill.params.fixed_angle;
|
f->fixed_angle = surface_fill.params.fixed_angle;
|
||||||
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
|
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
|
||||||
|
f->tpms_radial_field = this->object()->tpms_radial_field(surface_fill.params.tpms_adaptive);
|
||||||
f->print_config = &this->object()->print()->config();
|
f->print_config = &this->object()->print()->config();
|
||||||
f->print_object_config = &this->object()->config();
|
f->print_object_config = &this->object()->config();
|
||||||
|
|
||||||
@@ -1614,6 +1638,9 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
|
|||||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||||
params.multiline = surface_fill.params.multiline;
|
params.multiline = surface_fill.params.multiline;
|
||||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||||
|
params.tpms_adaptive = surface_fill.params.tpms_adaptive;
|
||||||
|
params.tpms_interior_density = float(0.01 * surface_fill.params.tpms_interior_density);
|
||||||
|
params.tpms_adaptive_gradient = surface_fill.params.tpms_adaptive_gradient;
|
||||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||||
// Orca: Match make_fills() when choosing the origin of plane-path patterns.
|
// Orca: Match make_fills() when choosing the origin of plane-path patterns.
|
||||||
// Without the sparse extrusion role, the filler uses each surface's bounds
|
// Without the sparse extrusion role, the filler uses each surface's bounds
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace Slic3r { class ExtrusionEntityCollection; }
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class Surface;
|
class Surface;
|
||||||
|
class TpmsRadialField;
|
||||||
enum InfillPattern : int;
|
enum InfillPattern : int;
|
||||||
|
|
||||||
namespace FillAdaptive {
|
namespace FillAdaptive {
|
||||||
@@ -91,6 +92,11 @@ struct FillParams
|
|||||||
// For Gyroid: when true, use the parameterized "optimized" variant.
|
// For Gyroid: when true, use the parameterized "optimized" variant.
|
||||||
bool gyroid_optimized { false };
|
bool gyroid_optimized { false };
|
||||||
|
|
||||||
|
// For TPMS: grade the density from the surface to the interior of the object. Density fraction.
|
||||||
|
TpmsAdaptiveMode tpms_adaptive { TpmsAdaptiveMode::Disabled };
|
||||||
|
float tpms_interior_density { 0.f };
|
||||||
|
TpmsAdaptiveGradient tpms_adaptive_gradient { TpmsAdaptiveGradient::Linear };
|
||||||
|
|
||||||
// Orca: corner smoothing factor in the range [0, 1].
|
// Orca: corner smoothing factor in the range [0, 1].
|
||||||
double smooth_factor { 0. };
|
double smooth_factor { 0. };
|
||||||
|
|
||||||
@@ -155,6 +161,9 @@ public:
|
|||||||
// Octree builds on mesh for usage in the adaptive cubic infill
|
// Octree builds on mesh for usage in the adaptive cubic infill
|
||||||
FillAdaptive::Octree* adapt_fill_octree = nullptr;
|
FillAdaptive::Octree* adapt_fill_octree = nullptr;
|
||||||
|
|
||||||
|
// Radial coordinate inside the object for the adaptive TPMS infill
|
||||||
|
const TpmsRadialField* tpms_radial_field = nullptr;
|
||||||
|
|
||||||
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
|
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
|
||||||
// Orca: also used by gap fill function.
|
// Orca: also used by gap fill function.
|
||||||
const PrintConfig *print_config = nullptr;
|
const PrintConfig *print_config = nullptr;
|
||||||
|
|||||||
@@ -17,6 +17,17 @@
|
|||||||
#include "libslic3r/Polyline.hpp"
|
#include "libslic3r/Polyline.hpp"
|
||||||
#include "FillGyroid.hpp"
|
#include "FillGyroid.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "FillTpmsAdaptive.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
static float gyroid(float x, float y, float z)
|
||||||
|
{
|
||||||
|
return std::sin(x) * std::cos(y) + std::sin(y) * std::cos(z) + std::sin(z) * std::cos(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Marching-squares scalar field for the optimized gyroid branch.
|
// Marching-squares scalar field for the optimized gyroid branch.
|
||||||
@@ -62,10 +73,7 @@ struct GyroidField
|
|||||||
|
|
||||||
float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const
|
float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const
|
||||||
{
|
{
|
||||||
const float a = fx * float(x);
|
return gyroid(fx * float(x), fy * float(y), fz * float(z_arg));
|
||||||
const float b = fy * float(y);
|
|
||||||
const float c = fz * float(z_arg);
|
|
||||||
return std::sin(a) * std::cos(b) + std::sin(b) * std::cos(c) + std::sin(c) * std::cos(a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float get_scalar(Coord p) const
|
float get_scalar(Coord p) const
|
||||||
@@ -307,6 +315,14 @@ void FillGyroid::_fill_surface_single(
|
|||||||
ExPolygon expolygon,
|
ExPolygon expolygon,
|
||||||
Polylines &polylines_out)
|
Polylines &polylines_out)
|
||||||
{
|
{
|
||||||
|
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
|
||||||
|
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
|
||||||
|
[&](const FillParams &shell_params, const ExPolygon &shell) {
|
||||||
|
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
|
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
|
||||||
if(std::abs(infill_angle) >= EPSILON)
|
if(std::abs(infill_angle) >= EPSILON)
|
||||||
expolygon.rotate(-infill_angle);
|
expolygon.rotate(-infill_angle);
|
||||||
@@ -326,7 +342,12 @@ void FillGyroid::_fill_surface_single(
|
|||||||
|
|
||||||
// generate pattern
|
// generate pattern
|
||||||
Polylines polylines;
|
Polylines polylines;
|
||||||
if (params.gyroid_optimized) {
|
if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
|
||||||
|
// Radians per mm of the regular pattern at a density.
|
||||||
|
auto frequency = [¶ms, this](double density) { return density * DensityAdjust / (params.multiline * this->spacing); };
|
||||||
|
polylines = make_adaptive_tpms({gyroid, frequency(params.density), frequency(params.tpms_interior_density), params.tpms_adaptive_gradient},
|
||||||
|
*this->tpms_radial_field, bb, this->z, params.layer_height, this->spacing, infill_angle);
|
||||||
|
} else if (params.gyroid_optimized) {
|
||||||
// Marching-squares path on the gyroid implicit field. Base period matches
|
// Marching-squares path on the gyroid implicit field. Base period matches
|
||||||
// the standard parametric path's wavelength: 2*pi * spacing / density_adj.
|
// the standard parametric path's wavelength: 2*pi * spacing / density_adj.
|
||||||
// omega >= 1 always, so fz >= baseline -> shorter vertical wavelength ->
|
// omega >= 1 always, so fz >= baseline -> shorter vertical wavelength ->
|
||||||
|
|||||||
@@ -0,0 +1,823 @@
|
|||||||
|
#include "FillTpmsAdaptive.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <tbb/blocked_range.h>
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
|
#include "../BoundingBox.hpp"
|
||||||
|
#include "../ClipperUtils.hpp"
|
||||||
|
#include "../ExPolygon.hpp"
|
||||||
|
#include "FillBase.hpp"
|
||||||
|
#include "../Execution/ExecutionTBB.hpp"
|
||||||
|
#include "../MarchingSquares.hpp"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
#include "../Polygon.hpp"
|
||||||
|
#include "../Polyline.hpp"
|
||||||
|
#include "../PrintConfig.hpp"
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// At most 4 MB of body indices; finer cells would not change the grading.
|
||||||
|
constexpr double MaxNodes = double(1 << 20);
|
||||||
|
constexpr double MinCellSize = 0.5;
|
||||||
|
|
||||||
|
// Two deepest points are in separate lobes when the depth between them drops below this ratio of the shallower one.
|
||||||
|
constexpr double NeckRatio = 0.8;
|
||||||
|
// Lobes shallower than this ratio of the deepest one of their body are graded as part of it.
|
||||||
|
constexpr double MinLobeRatio = 0.3;
|
||||||
|
// A lobe reaches twice as far as the side towards its neighbour, so that the side is half way to the surface.
|
||||||
|
constexpr double LobeReach = 2.;
|
||||||
|
// Width of the morph between the patterns of two lobes, in their distance to the center over its depth.
|
||||||
|
constexpr double LobeMorph = 0.1;
|
||||||
|
// Densities of the levels of Stepped shells, and of Smooth blend, are at most these ratios apart. Fewer levels blend
|
||||||
|
// with fewer lines running along the blends.
|
||||||
|
constexpr double ShellRatio = 1.5;
|
||||||
|
constexpr double BlendRatio = 2.5;
|
||||||
|
// Distance warp: samples of the mean depth along a ray.
|
||||||
|
constexpr int Samples = 32;
|
||||||
|
|
||||||
|
constexpr float InfF = std::numeric_limits<float>::infinity();
|
||||||
|
constexpr double InfD = std::numeric_limits<double>::infinity();
|
||||||
|
|
||||||
|
// Squared distance transform of a line (Felzenszwalb & Huttenlocher); infinite samples are no sites.
|
||||||
|
void distance_transform_line(const float *f, float *d, int n, int *v, double *s)
|
||||||
|
{
|
||||||
|
int k = -1;
|
||||||
|
for (int q = 0; q < n; ++q) {
|
||||||
|
if (f[q] == InfF)
|
||||||
|
continue;
|
||||||
|
double x = -InfD;
|
||||||
|
while (k >= 0) {
|
||||||
|
x = (f[q] + double(q) * q - f[v[k]] - double(v[k]) * v[k]) / (2. * (q - v[k]));
|
||||||
|
if (x > s[k])
|
||||||
|
break;
|
||||||
|
--k;
|
||||||
|
}
|
||||||
|
if (k < 0)
|
||||||
|
x = -InfD;
|
||||||
|
v[++k] = q;
|
||||||
|
s[k] = x;
|
||||||
|
s[k + 1] = InfD;
|
||||||
|
}
|
||||||
|
if (k < 0) {
|
||||||
|
std::fill(d, d + n, InfF);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int q = 0, j = 0; q < n; ++q) {
|
||||||
|
while (s[j + 1] < q)
|
||||||
|
++j;
|
||||||
|
d[q] = float(sqr(double(q - v[j])) + f[v[j]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void distance_transform_axis(std::vector<float> &grid, const Vec3i32 &size, int axis, const std::function<void()> &throw_if_canceled)
|
||||||
|
{
|
||||||
|
const int n = size[axis];
|
||||||
|
const int a1 = (axis + 1) % 3;
|
||||||
|
const int a2 = (axis + 2) % 3;
|
||||||
|
const size_t stride = axis == 0 ? 1 : axis == 1 ? size_t(size.x()) : size_t(size.x()) * size.y();
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, size_t(size[a1]) * size[a2]), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
std::vector<float> f(n), d(n);
|
||||||
|
std::vector<int> v(n);
|
||||||
|
std::vector<double> s(n + 1);
|
||||||
|
for (size_t line = range.begin(); line < range.end(); ++line) {
|
||||||
|
Vec3i32 idx;
|
||||||
|
idx[axis] = 0;
|
||||||
|
idx[a1] = int(line % size[a1]);
|
||||||
|
idx[a2] = int(line / size[a1]);
|
||||||
|
const size_t first = (size_t(idx.z()) * size.y() + idx.y()) * size.x() + idx.x();
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
f[i] = grid[first + i * stride];
|
||||||
|
distance_transform_line(f.data(), d.data(), n, v.data(), s.data());
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
grid[first + i * stride] = d[i];
|
||||||
|
}
|
||||||
|
throw_if_canceled();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks the nodes inside the expolygons with infinity, by even-odd scanlines.
|
||||||
|
void rasterize(const ExPolygons &expolygons, const Vec2d &origin, double cell, int nx, int ny, float *nodes)
|
||||||
|
{
|
||||||
|
std::vector<std::vector<double>> crossings(ny);
|
||||||
|
auto add_crossings = [&](const Polygon &polygon) {
|
||||||
|
const Points &pts = polygon.points;
|
||||||
|
for (size_t i = 0; i < pts.size(); ++i) {
|
||||||
|
const Vec2d a = unscaled(pts[i]);
|
||||||
|
const Vec2d b = unscaled(pts[i + 1 == pts.size() ? 0 : i + 1]);
|
||||||
|
if (a.y() == b.y())
|
||||||
|
continue;
|
||||||
|
const auto [lo, hi] = std::minmax(a.y(), b.y());
|
||||||
|
const int j0 = std::max(0, int(std::ceil((lo - origin.y()) / cell)));
|
||||||
|
const int j1 = std::min(ny, int(std::ceil((hi - origin.y()) / cell)));
|
||||||
|
for (int j = j0; j < j1; ++j) {
|
||||||
|
const double y = origin.y() + j * cell;
|
||||||
|
crossings[j].push_back(a.x() + (b.x() - a.x()) * (y - a.y()) / (b.y() - a.y()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const ExPolygon &expolygon : expolygons) {
|
||||||
|
add_crossings(expolygon.contour);
|
||||||
|
for (const Polygon &hole : expolygon.holes)
|
||||||
|
add_crossings(hole);
|
||||||
|
}
|
||||||
|
for (int j = 0; j < ny; ++j) {
|
||||||
|
std::vector<double> &xs = crossings[j];
|
||||||
|
std::sort(xs.begin(), xs.end());
|
||||||
|
for (size_t k = 0; k + 1 < xs.size(); k += 2) {
|
||||||
|
const int i0 = std::max(0, int(std::ceil((xs[k] - origin.x()) / cell)));
|
||||||
|
const int i1 = std::min(nx, int(std::ceil((xs[k + 1] - origin.x()) / cell)));
|
||||||
|
std::fill(nodes + size_t(j) * nx + std::min(i0, i1), nodes + size_t(j) * nx + i1, InfF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale of the pattern relative to the surface at a depth from 0 at the surface to 1 at the deepest point.
|
||||||
|
double target_scale(double ratio, TpmsAdaptiveGradient gradient, double depth)
|
||||||
|
{
|
||||||
|
switch (gradient) {
|
||||||
|
case TpmsAdaptiveGradient::Quadratic: return 1. + (ratio - 1.) * depth * depth;
|
||||||
|
case TpmsAdaptiveGradient::Exponential: return std::pow(ratio, depth);
|
||||||
|
default: return 1. + (ratio - 1.) * depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels of Stepped shells and Smooth blend, geometric from the surface at 0 to the interior at count, and the
|
||||||
|
// continuous level of the target of a depth.
|
||||||
|
struct DensityLevels
|
||||||
|
{
|
||||||
|
DensityLevels(double ratio, double step, TpmsAdaptiveGradient gradient)
|
||||||
|
: ratio(ratio), gradient(gradient), count(int(std::ceil(std::abs(std::log(ratio)) / std::log(step) - EPSILON)))
|
||||||
|
{}
|
||||||
|
|
||||||
|
double scale(int level) const { return count == 0 ? 1. : std::pow(ratio, double(level) / count); }
|
||||||
|
double level(double depth) const
|
||||||
|
{
|
||||||
|
return count == 0 ? 0. : count * std::log(target_scale(ratio, gradient, depth)) / std::log(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ratio;
|
||||||
|
TpmsAdaptiveGradient gradient;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scale of the pattern around the center at a radial coordinate t. The mean cell scale over the ball of radius t,
|
||||||
|
// t^-3 * integral of 3 t'^2 * target(t'), or over the disc in 2D, follows the gradient; beyond the surface the target
|
||||||
|
// is the surface scale.
|
||||||
|
class RadialScale
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RadialScale(const AdaptiveTpms &tpms, int dimensions) : m_dimensions(dimensions)
|
||||||
|
{
|
||||||
|
const double ratio = std::max(tpms.interior_frequency / tpms.surface_frequency, 1e-3);
|
||||||
|
auto target = [&tpms, ratio](double depth) { return target_scale(ratio, tpms.gradient, depth); };
|
||||||
|
m_scale[0] = target(1.);
|
||||||
|
double volume = 0.;
|
||||||
|
for (size_t i = 1; i < m_scale.size(); ++i) {
|
||||||
|
const double t0 = double(i - 1) / double(m_scale.size() - 1);
|
||||||
|
const double t1 = double(i) / double(m_scale.size() - 1);
|
||||||
|
volume += (std::pow(t1, m_dimensions) - std::pow(t0, m_dimensions)) * target(1. - 0.5 * (t0 + t1));
|
||||||
|
m_scale[i] = volume / std::pow(t1, m_dimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double operator()(double t) const
|
||||||
|
{
|
||||||
|
if (t >= 1.) {
|
||||||
|
const double volume = std::pow(t, m_dimensions);
|
||||||
|
return (m_scale.back() + volume - 1.) / volume;
|
||||||
|
}
|
||||||
|
const double x = t * double(m_scale.size() - 1);
|
||||||
|
const size_t i = std::min(size_t(x), m_scale.size() - 2);
|
||||||
|
return m_scale[i] + (m_scale[i + 1] - m_scale[i]) * (x - double(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_dimensions;
|
||||||
|
std::array<double, 257> m_scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TpmsRadialField::TpmsRadialField(const std::vector<Slice> &slices, const BoundingBox &bbox, TpmsAdaptiveMode mode,
|
||||||
|
const std::function<void()> &throw_if_canceled)
|
||||||
|
: m_mode(mode)
|
||||||
|
, m_axis(mode == TpmsAdaptiveMode::NormalX ? 0 : mode == TpmsAdaptiveMode::NormalY ? 1 : mode == TpmsAdaptiveMode::NormalZ ? 2 : -1)
|
||||||
|
{
|
||||||
|
assert(!slices.empty() && mode != TpmsAdaptiveMode::Disabled);
|
||||||
|
const Vec3d min(unscaled(bbox.min.x()), unscaled(bbox.min.y()), slices.front().bottom_z);
|
||||||
|
const Vec3d extent = Vec3d(unscaled(bbox.max.x()), unscaled(bbox.max.y()), slices.back().top_z) - min;
|
||||||
|
|
||||||
|
// Padded by a node on each side, so that the border of the grid is outside.
|
||||||
|
m_cell = std::max(MinCellSize, std::cbrt(extent.prod() / MaxNodes));
|
||||||
|
auto nodes = [this](double length) { return int(std::ceil(length / m_cell)) + 3; };
|
||||||
|
while (double(nodes(extent.x())) * nodes(extent.y()) * nodes(extent.z()) > MaxNodes)
|
||||||
|
m_cell *= 1.1;
|
||||||
|
m_size = Vec3i32(nodes(extent.x()), nodes(extent.y()), nodes(extent.z()));
|
||||||
|
m_origin = min - Vec3d::Constant(m_cell);
|
||||||
|
|
||||||
|
const size_t sy = size_t(m_size.x());
|
||||||
|
const size_t sz = sy * m_size.y();
|
||||||
|
std::vector<float> depth(sz * m_size.z(), 0.f);
|
||||||
|
tbb::parallel_for(tbb::blocked_range<int>(0, m_size.z()), [&](const tbb::blocked_range<int> &range) {
|
||||||
|
for (int k = range.begin(); k < range.end(); ++k) {
|
||||||
|
const double z = m_origin.z() + k * m_cell;
|
||||||
|
auto it = std::lower_bound(slices.begin(), slices.end(), z, [](const Slice &s, double z) { return s.top_z < z; });
|
||||||
|
if (it != slices.end() && z > it->bottom_z)
|
||||||
|
rasterize(*it->expolygons, m_origin.head<2>(), m_cell, m_size.x(), m_size.y(), depth.data() + k * sz);
|
||||||
|
}
|
||||||
|
throw_if_canceled();
|
||||||
|
});
|
||||||
|
for (int axis = 0; axis < 3; ++axis)
|
||||||
|
if (axis != m_axis)
|
||||||
|
distance_transform_axis(depth, m_size, axis, throw_if_canceled);
|
||||||
|
|
||||||
|
auto position = [this, sy, sz](size_t i) {
|
||||||
|
return Vec3d(m_origin + m_cell * Vec3d(double(i % sy), double(i / sy % m_size.y()), double(i / sz)));
|
||||||
|
};
|
||||||
|
const std::array<std::ptrdiff_t, 6> steps{1, -1, std::ptrdiff_t(sy), -std::ptrdiff_t(sy), std::ptrdiff_t(sz), -std::ptrdiff_t(sz)};
|
||||||
|
|
||||||
|
auto node_of = [this, sy, sz](const Vec3d &pt) -> std::ptrdiff_t {
|
||||||
|
const Vec3d f = (pt - m_origin) / m_cell;
|
||||||
|
const long x = std::lround(f.x()), y = std::lround(f.y()), z = std::lround(f.z());
|
||||||
|
if (x < 0 || y < 0 || z < 0 || x >= m_size.x() || y >= m_size.y() || z >= m_size.z())
|
||||||
|
return -1;
|
||||||
|
return std::ptrdiff_t(size_t(z) * sz + size_t(y) * sy + size_t(x));
|
||||||
|
};
|
||||||
|
// In the 2D modes, the steps within a section.
|
||||||
|
auto in_section = [this](size_t step) { return int(step / 2) != m_axis; };
|
||||||
|
std::vector<std::ptrdiff_t> neighbours;
|
||||||
|
for (int dz = -1; dz <= 1; ++dz)
|
||||||
|
for (int dy = -1; dy <= 1; ++dy)
|
||||||
|
for (int dx = -1; dx <= 1; ++dx)
|
||||||
|
if ((dx != 0 || dy != 0 || dz != 0) && (m_axis < 0 || Vec3i32(dx, dy, dz)[m_axis] == 0))
|
||||||
|
neighbours.push_back(std::ptrdiff_t(dz) * std::ptrdiff_t(sz) + std::ptrdiff_t(dy) * std::ptrdiff_t(sy) + dx);
|
||||||
|
|
||||||
|
// Bodies are the connected inside nodes, none of which is on the border. The deepest nodes of a body are the
|
||||||
|
// centers of its lobes, unless the depth between them stays above NeckRatio; where the depth ties, the center
|
||||||
|
// is the node nearest to the middle of the tied nodes.
|
||||||
|
const bool lobes = m_mode != TpmsAdaptiveMode::SteppedShells && m_mode != TpmsAdaptiveMode::SmoothBlend;
|
||||||
|
m_body.assign(depth.size(), -1);
|
||||||
|
std::vector<size_t> body_nodes;
|
||||||
|
for (size_t seed = 0; seed < depth.size(); ++seed) {
|
||||||
|
if (depth[seed] == 0.f || m_body[seed] >= 0)
|
||||||
|
continue;
|
||||||
|
const int id = int(m_bodies.size());
|
||||||
|
body_nodes.assign(1, seed);
|
||||||
|
m_body[seed] = id;
|
||||||
|
float max_depth = 0.f;
|
||||||
|
for (size_t k = 0; k < body_nodes.size(); ++k) {
|
||||||
|
max_depth = std::max(max_depth, depth[body_nodes[k]]);
|
||||||
|
for (size_t s = 0; s < steps.size(); ++s)
|
||||||
|
if (const size_t j = body_nodes[k] + steps[s]; in_section(s) && depth[j] > 0.f && m_body[j] < 0) {
|
||||||
|
m_body[j] = id;
|
||||||
|
body_nodes.push_back(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_bodies.push_back({m_lobes.size(), 0, (std::sqrt(double(max_depth)) - 0.5) * m_cell});
|
||||||
|
if (!lobes)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<size_t> peaks;
|
||||||
|
for (size_t i : body_nodes)
|
||||||
|
if (depth[i] >= sqr(MinLobeRatio) * max_depth &&
|
||||||
|
std::all_of(neighbours.begin(), neighbours.end(), [&](std::ptrdiff_t n) { return depth[i + n] <= depth[i]; }))
|
||||||
|
peaks.push_back(i);
|
||||||
|
std::sort(peaks.begin(), peaks.end(), [&depth](size_t a, size_t b) { return depth[a] > depth[b] || (depth[a] == depth[b] && a < b); });
|
||||||
|
auto necked = [&](size_t a, size_t b) {
|
||||||
|
const Vec3d pa = position(a), pb = position(b);
|
||||||
|
const double limit = sqr(NeckRatio) * std::min(depth[a], depth[b]);
|
||||||
|
const int samples = int(std::ceil((pb - pa).norm() / (0.5 * m_cell)));
|
||||||
|
for (int s = 1; s < samples; ++s)
|
||||||
|
if (depth[node_of(pa + (pb - pa) * (double(s) / samples))] < limit)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// A peak joins the first lobe it sees without a neck, if it is as deep. The lobes are made one at a time,
|
||||||
|
// from the first remaining peak, testing the others in parallel.
|
||||||
|
std::vector<std::vector<size_t>> ties;
|
||||||
|
while (!peaks.empty()) {
|
||||||
|
const size_t front = peaks.front();
|
||||||
|
std::vector<char> joins(peaks.size(), 0);
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(1, peaks.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t k = range.begin(); k < range.end(); ++k)
|
||||||
|
if (!necked(front, peaks[k]))
|
||||||
|
joins[k] = std::sqrt(depth[peaks[k]]) >= std::sqrt(depth[front]) - 1.f ? 1 : 2;
|
||||||
|
});
|
||||||
|
std::vector<size_t> &tied = ties.emplace_back(1, front);
|
||||||
|
std::vector<size_t> remaining;
|
||||||
|
for (size_t k = 1; k < peaks.size(); ++k)
|
||||||
|
if (joins[k] == 0)
|
||||||
|
remaining.push_back(peaks[k]);
|
||||||
|
else if (joins[k] == 1)
|
||||||
|
tied.push_back(peaks[k]);
|
||||||
|
peaks = std::move(remaining);
|
||||||
|
}
|
||||||
|
m_bodies.back().lobes = ties.size();
|
||||||
|
for (const std::vector<size_t> &tied : ties) {
|
||||||
|
Vec3d middle(0., 0., 0.);
|
||||||
|
for (size_t i : tied)
|
||||||
|
middle += position(i);
|
||||||
|
middle /= double(tied.size());
|
||||||
|
Vec3d center = position(tied.front());
|
||||||
|
for (size_t i : tied)
|
||||||
|
if ((position(i) - middle).squaredNorm() < (center - middle).squaredNorm())
|
||||||
|
center = position(i);
|
||||||
|
m_lobes.push_back({center, (std::sqrt(double(depth[tied.front()])) - 0.5) * m_cell, {}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw_if_canceled();
|
||||||
|
|
||||||
|
if (m_mode == TpmsAdaptiveMode::SteppedShells || m_mode == TpmsAdaptiveMode::SmoothBlend || m_mode == TpmsAdaptiveMode::DistanceWarp) {
|
||||||
|
m_depth.assign(depth.size(), 0.f);
|
||||||
|
for (size_t i = 0; i < depth.size(); ++i)
|
||||||
|
if (m_body[i] >= 0)
|
||||||
|
m_depth[i] = float(std::min(1., std::max(0., std::sqrt(double(depth[i])) - 0.5) * m_cell / m_bodies[m_body[i]].depth));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The reach of a lobe is the first exit along each direction from its center, smoothed over the directions.
|
||||||
|
// It is shortened towards a neighbouring lobe, from where the point is nearer to the other lobe relative to their depths.
|
||||||
|
std::vector<int> lobe_body(m_lobes.size());
|
||||||
|
for (size_t id = 0; id < m_bodies.size(); ++id)
|
||||||
|
std::fill_n(lobe_body.begin() + m_bodies[id].first_lobe, m_bodies[id].lobes, int(id));
|
||||||
|
const int rows = this->directions() / Azimuth;
|
||||||
|
auto direction = [this](int i, int j) {
|
||||||
|
const double azimuth = j * 2. * PI / Azimuth;
|
||||||
|
Vec3d dir = Vec3d::Zero();
|
||||||
|
if (m_axis < 0) {
|
||||||
|
const double polar = (i + 0.5) * PI / Polar;
|
||||||
|
dir = Vec3d(std::sin(polar) * std::cos(azimuth), std::sin(polar) * std::sin(azimuth), std::cos(polar));
|
||||||
|
} else {
|
||||||
|
dir[(m_axis + 1) % 3] = std::cos(azimuth);
|
||||||
|
dir[(m_axis + 2) % 3] = std::sin(azimuth);
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
// Two passes of a box filter over the neighbouring directions, for each of the values of a direction.
|
||||||
|
auto smooth = [rows](std::vector<double> &values, size_t count) {
|
||||||
|
for (int pass = 0; pass < 2; ++pass) {
|
||||||
|
std::vector<double> smoothed(values.size(), 0.);
|
||||||
|
for (int i = 0; i < rows; ++i)
|
||||||
|
for (int j = 0; j < Azimuth; ++j)
|
||||||
|
for (size_t k = 0; k < count; ++k) {
|
||||||
|
double &sum = smoothed[size_t(i * Azimuth + j) * count + k];
|
||||||
|
for (int di = -1; di <= 1; ++di)
|
||||||
|
for (int dj = -1; dj <= 1; ++dj)
|
||||||
|
sum += values[size_t(std::clamp(i + di, 0, rows - 1) * Azimuth + (j + dj + Azimuth) % Azimuth) * count + k];
|
||||||
|
sum /= 9.;
|
||||||
|
}
|
||||||
|
values = std::move(smoothed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_lobes.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t l = range.begin(); l < range.end(); ++l) {
|
||||||
|
const int id = lobe_body[l];
|
||||||
|
const Body &body = m_bodies[id];
|
||||||
|
Lobe &lobe = m_lobes[l];
|
||||||
|
// The other lobes of the body, by the distance from the center beyond which they may be nearer.
|
||||||
|
std::vector<std::pair<double, size_t>> others;
|
||||||
|
for (size_t k = body.first_lobe; k < body.first_lobe + body.lobes; ++k)
|
||||||
|
if (k != l)
|
||||||
|
others.emplace_back((m_lobes[k].center - lobe.center).norm() / (1. + m_lobes[k].depth / lobe.depth), k);
|
||||||
|
std::sort(others.begin(), others.end());
|
||||||
|
auto nearer_lobe = [&](const Vec3d &pt, double r) {
|
||||||
|
for (auto it = others.begin(); it != others.end() && it->first < r; ++it)
|
||||||
|
if ((pt - m_lobes[it->second].center).norm() / m_lobes[it->second].depth < r / lobe.depth)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const double step = 0.5 * m_cell;
|
||||||
|
std::vector<double> log_reach(this->directions());
|
||||||
|
for (int i = 0; i < rows; ++i)
|
||||||
|
for (int j = 0; j < Azimuth; ++j) {
|
||||||
|
const Vec3d dir = direction(i, j);
|
||||||
|
double r = 0.;
|
||||||
|
double limit = InfD;
|
||||||
|
for (;;) {
|
||||||
|
const Vec3d pt = lobe.center + (r + step) * dir;
|
||||||
|
const std::ptrdiff_t n = node_of(pt);
|
||||||
|
if (n < 0 || depth[n] == 0.f || m_body[n] != id || r + step >= limit)
|
||||||
|
break;
|
||||||
|
if (limit == InfD && nearer_lobe(pt, r + step))
|
||||||
|
limit = LobeReach * (r + step);
|
||||||
|
r += step;
|
||||||
|
}
|
||||||
|
log_reach[i * Azimuth + j] = std::log(std::min(r + 0.5 * step, limit));
|
||||||
|
}
|
||||||
|
smooth(log_reach, 1);
|
||||||
|
lobe.reach.resize(log_reach.size());
|
||||||
|
std::transform(log_reach.begin(), log_reach.end(), lobe.reach.begin(), [](double v) { return float(std::exp(v)); });
|
||||||
|
|
||||||
|
if (m_mode == TpmsAdaptiveMode::DistanceWarp) {
|
||||||
|
std::vector<double> mean(log_reach.size() * (Samples + 1));
|
||||||
|
for (size_t d = 0; d < log_reach.size(); ++d) {
|
||||||
|
const Vec3d dir = direction(int(d) / Azimuth, int(d) % Azimuth);
|
||||||
|
double integral = 0.;
|
||||||
|
double previous = this->depth(lobe.center);
|
||||||
|
mean[d * (Samples + 1)] = previous;
|
||||||
|
for (int k = 1; k <= Samples; ++k) {
|
||||||
|
// Exact for a depth linear between the samples, a + b * tau, weighted by tau^2.
|
||||||
|
const double t0 = double(k - 1) / Samples;
|
||||||
|
const double t1 = double(k) / Samples;
|
||||||
|
const double d1 = this->depth(lobe.center + t1 * lobe.reach[d] * dir);
|
||||||
|
const double b = (d1 - previous) * Samples;
|
||||||
|
const double a = previous - b * t0;
|
||||||
|
integral += a * (std::pow(t1, 3) - std::pow(t0, 3)) / 3. + b * (std::pow(t1, 4) - std::pow(t0, 4)) / 4.;
|
||||||
|
previous = d1;
|
||||||
|
mean[d * (Samples + 1) + k] = 3. * integral / std::pow(t1, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Smoothed like the reach: sharper profiles shear the pattern across the layer, adding lines.
|
||||||
|
smooth(mean, Samples + 1);
|
||||||
|
lobe.mean_depth.assign(mean.begin(), mean.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw_if_canceled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Every other node belongs to its nearest body, within its section in the 2D modes.
|
||||||
|
if (m_axis >= 0) {
|
||||||
|
std::vector<bool> has_body(m_size[m_axis], false);
|
||||||
|
for (size_t i = 0; i < m_body.size(); ++i)
|
||||||
|
if (m_body[i] >= 0)
|
||||||
|
has_body[m_axis == 0 ? i % sy : m_axis == 1 ? i / sy % m_size.y() : i / sz] = true;
|
||||||
|
m_section.assign(m_size[m_axis], -1);
|
||||||
|
for (int k = 0; k < m_size[m_axis]; ++k)
|
||||||
|
for (int d = 0; d < m_size[m_axis] && m_section[k] < 0; ++d)
|
||||||
|
if (k - d >= 0 && has_body[k - d])
|
||||||
|
m_section[k] = k - d;
|
||||||
|
else if (k + d < m_size[m_axis] && has_body[k + d])
|
||||||
|
m_section[k] = k + d;
|
||||||
|
}
|
||||||
|
// Stepped shells and Smooth blend only look up the depth.
|
||||||
|
if (!lobes) {
|
||||||
|
m_body = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_bodies.size() == 1) {
|
||||||
|
std::fill(m_body.begin(), m_body.end(), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::deque<size_t> queue;
|
||||||
|
for (size_t i = 0; i < m_body.size(); ++i)
|
||||||
|
if (m_body[i] >= 0)
|
||||||
|
queue.push_back(i);
|
||||||
|
while (!queue.empty()) {
|
||||||
|
const size_t i = queue.front();
|
||||||
|
queue.pop_front();
|
||||||
|
const size_t x = i % sy, y = i / sy % m_size.y(), z = i / sz;
|
||||||
|
const std::array<bool, 6> valid{x + 1 < sy, x > 0, y + 1 < size_t(m_size.y()), y > 0, z + 1 < size_t(m_size.z()), z > 0};
|
||||||
|
for (size_t k = 0; k < steps.size(); ++k)
|
||||||
|
if (valid[k] && in_section(k) && m_body[i + steps[k]] < 0) {
|
||||||
|
m_body[i + steps[k]] = m_body[i];
|
||||||
|
queue.push_back(i + steps[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double TpmsRadialField::depth(const Vec3d &pt) const
|
||||||
|
{
|
||||||
|
assert(!m_depth.empty());
|
||||||
|
const Vec3d f = (pt - m_origin) / m_cell;
|
||||||
|
std::array<int, 3> n0;
|
||||||
|
std::array<double, 3> w;
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
n0[a] = std::clamp(int(std::floor(f[a])), 0, m_size[a] - 2);
|
||||||
|
w[a] = std::clamp(f[a] - n0[a], 0., 1.);
|
||||||
|
}
|
||||||
|
const size_t sy = size_t(m_size.x());
|
||||||
|
const size_t sz = sy * size_t(m_size.y());
|
||||||
|
double value = 0.;
|
||||||
|
for (int c = 0; c < 8; ++c) {
|
||||||
|
const size_t n = size_t(n0[2] + (c >> 2)) * sz + size_t(n0[1] + (c >> 1 & 1)) * sy + size_t(n0[0] + (c & 1));
|
||||||
|
value += (c & 1 ? w[0] : 1. - w[0]) * (c >> 1 & 1 ? w[1] : 1. - w[1]) * (c >> 2 ? w[2] : 1. - w[2]) * m_depth[n];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d TpmsRadialField::offset(const Vec3d &pt, const Vec3d ¢er) const
|
||||||
|
{
|
||||||
|
Vec3d d = pt - center;
|
||||||
|
if (m_axis >= 0)
|
||||||
|
d[m_axis] = 0.;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
double TpmsRadialField::radial(const Lobe &lobe, const Vec3d &pt) const
|
||||||
|
{
|
||||||
|
const Vec3d d = this->offset(pt, lobe.center);
|
||||||
|
const double r = d.norm();
|
||||||
|
// Distance warp: the radial coordinate of the linear profile with the same mean depth, which is 1 - 3/4 of it.
|
||||||
|
auto warp = [](double mean_depth) { return std::max(0., 4. / 3. * (1. - mean_depth)); };
|
||||||
|
if (r < EPSILON)
|
||||||
|
return lobe.mean_depth.empty() ? 0. : warp(lobe.mean_depth.front());
|
||||||
|
int i = 0;
|
||||||
|
double fi = 0.;
|
||||||
|
double azimuth;
|
||||||
|
if (m_axis < 0) {
|
||||||
|
const double polar = std::clamp(std::acos(std::clamp(d.z() / r, -1., 1.)) / PI * Polar - 0.5, 0., double(Polar - 1));
|
||||||
|
i = std::min(int(polar), Polar - 2);
|
||||||
|
fi = polar - i;
|
||||||
|
azimuth = std::atan2(d.y(), d.x());
|
||||||
|
} else
|
||||||
|
azimuth = std::atan2(d[(m_axis + 2) % 3], d[(m_axis + 1) % 3]);
|
||||||
|
azimuth *= Azimuth / (2. * PI);
|
||||||
|
if (azimuth < 0.)
|
||||||
|
azimuth += Azimuth;
|
||||||
|
const int j0 = int(azimuth) % Azimuth;
|
||||||
|
const int j1 = (j0 + 1) % Azimuth;
|
||||||
|
const double fj = azimuth - std::floor(azimuth);
|
||||||
|
auto at = [&lobe](int i, int j) { return double(lobe.reach[i * Azimuth + j]); };
|
||||||
|
double reach = at(i, j0) * (1. - fj) + at(i, j1) * fj;
|
||||||
|
if (fi > 0.)
|
||||||
|
reach = reach * (1. - fi) + (at(i + 1, j0) * (1. - fj) + at(i + 1, j1) * fj) * fi;
|
||||||
|
const double tau = r / reach;
|
||||||
|
if (lobe.mean_depth.empty())
|
||||||
|
return tau;
|
||||||
|
// Beyond the surface, the depth is zero.
|
||||||
|
auto mean_at = [&lobe, tau](int i, int j) {
|
||||||
|
const float *mean = lobe.mean_depth.data() + size_t(i * Azimuth + j) * (Samples + 1);
|
||||||
|
if (tau >= 1.)
|
||||||
|
return double(mean[Samples]) / (tau * tau * tau);
|
||||||
|
const double x = tau * Samples;
|
||||||
|
const int k = std::min(int(x), Samples - 1);
|
||||||
|
return mean[k] + (mean[k + 1] - mean[k]) * (x - k);
|
||||||
|
};
|
||||||
|
double mean = mean_at(i, j0) * (1. - fj) + mean_at(i, j1) * fj;
|
||||||
|
if (fi > 0.)
|
||||||
|
mean = mean * (1. - fi) + (mean_at(i + 1, j0) * (1. - fj) + mean_at(i + 1, j1) * fj) * fi;
|
||||||
|
return warp(mean);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t TpmsRadialField::radial(const Vec3d &pt, Radials &out) const
|
||||||
|
{
|
||||||
|
assert(!this->empty());
|
||||||
|
Vec3i32 idx;
|
||||||
|
for (int axis = 0; axis < 3; ++axis)
|
||||||
|
idx[axis] = std::clamp<int>(int(std::lround((pt[axis] - m_origin[axis]) / m_cell)), 0, m_size[axis] - 1);
|
||||||
|
auto node = [this](const Vec3i32 &idx) { return (size_t(idx.z()) * m_size.y() + idx.y()) * m_size.x() + idx.x(); };
|
||||||
|
if (m_axis < 0)
|
||||||
|
return this->body_radial(node(idx), pt, 1.f, out.data());
|
||||||
|
|
||||||
|
// The sections around pt, or the nearest ones with a body.
|
||||||
|
const double f = std::clamp((pt[m_axis] - m_origin[m_axis]) / m_cell, 0., double(m_size[m_axis] - 1));
|
||||||
|
const int k = std::min(int(f), m_size[m_axis] - 2);
|
||||||
|
const float w = float(f - k);
|
||||||
|
size_t count = 0;
|
||||||
|
if (w < 1.f) {
|
||||||
|
idx[m_axis] = m_section[k];
|
||||||
|
count += this->body_radial(node(idx), pt, 1.f - w, out.data());
|
||||||
|
}
|
||||||
|
if (w > 0.f) {
|
||||||
|
idx[m_axis] = m_section[k + 1];
|
||||||
|
count += this->body_radial(node(idx), pt, w, out.data() + count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t TpmsRadialField::body_radial(size_t node, const Vec3d &pt, float weight, Radial *out) const
|
||||||
|
{
|
||||||
|
const Body &body = m_bodies[m_body[node]];
|
||||||
|
if (body.lobes == 1) {
|
||||||
|
const Lobe &lobe = m_lobes[body.first_lobe];
|
||||||
|
out[0] = {lobe.center, radial(lobe, pt), weight};
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lobes nearest relative to their depth; they morph into each other near the sides where they are as near.
|
||||||
|
std::array<std::pair<double, size_t>, MaxMorph> nearest;
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t l = body.first_lobe; l < body.first_lobe + body.lobes; ++l) {
|
||||||
|
const double d = this->offset(pt, m_lobes[l].center).norm() / m_lobes[l].depth;
|
||||||
|
if (count < MaxMorph)
|
||||||
|
nearest[count++] = {d, l};
|
||||||
|
else if (d < nearest.back().first)
|
||||||
|
nearest.back() = {d, l};
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
for (size_t k = count - 1; k > 0 && nearest[k].first < nearest[k - 1].first; --k)
|
||||||
|
std::swap(nearest[k], nearest[k - 1]);
|
||||||
|
}
|
||||||
|
std::array<double, MaxMorph> blend;
|
||||||
|
double total = 0.;
|
||||||
|
size_t morphs = 0;
|
||||||
|
for (; morphs < count; ++morphs) {
|
||||||
|
const double u = 0.5 - (nearest[morphs].first - nearest[0].first) / LobeMorph;
|
||||||
|
if (u <= 0.)
|
||||||
|
break;
|
||||||
|
blend[morphs] = u * u * (3. - 2. * u);
|
||||||
|
total += blend[morphs];
|
||||||
|
}
|
||||||
|
for (size_t k = 0; k < morphs; ++k) {
|
||||||
|
const Lobe &lobe = m_lobes[nearest[k].second];
|
||||||
|
out[k] = {lobe.center, radial(lobe, pt), float(weight * blend[k] / total)};
|
||||||
|
}
|
||||||
|
return morphs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
namespace marchsq {
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
struct AdaptiveTpmsField
|
||||||
|
{
|
||||||
|
static constexpr float gsizef = 0.40f; // grid cell size in mm (roughly line segment length).
|
||||||
|
static constexpr float rsizef = 0.004f; // raster pixel size in mm (roughly point accuracy).
|
||||||
|
const coord_t rsize = scaled(rsizef);
|
||||||
|
const long gsize = std::lround(gsizef / rsizef);
|
||||||
|
|
||||||
|
const AdaptiveTpms &tpms;
|
||||||
|
const TpmsRadialField &radial_field;
|
||||||
|
RadialScale scale;
|
||||||
|
DensityLevels levels;
|
||||||
|
Point size;
|
||||||
|
Point offs;
|
||||||
|
double z;
|
||||||
|
double cos_angle;
|
||||||
|
double sin_angle;
|
||||||
|
|
||||||
|
AdaptiveTpmsField(const AdaptiveTpms &tpms, const TpmsRadialField &radial_field, const BoundingBox &bbox, coordf_t z, float angle)
|
||||||
|
: tpms(tpms), radial_field(radial_field), scale(tpms, radial_field.axis() < 0 ? 3 : 2)
|
||||||
|
, levels(std::max(tpms.interior_frequency / tpms.surface_frequency, 1e-3), BlendRatio, tpms.gradient)
|
||||||
|
, size(bbox.size()), offs(bbox.min), z(z)
|
||||||
|
, cos_angle(std::cos(angle)), sin_angle(std::sin(angle))
|
||||||
|
{}
|
||||||
|
|
||||||
|
// The pattern is scaled around the center of the lobe, morphing into the pattern of a neighbouring lobe near the
|
||||||
|
// side between them. In the 2D modes only within the section, with the interior frequency along the axis.
|
||||||
|
// The radial field is in the object frame, the fill is rotated by -angle.
|
||||||
|
float get_scalar(const Coord &p) const
|
||||||
|
{
|
||||||
|
const Point pt = to_Point(p);
|
||||||
|
const double x = unscaled(pt.x());
|
||||||
|
const double y = unscaled(pt.y());
|
||||||
|
const Vec3d obj(cos_angle * x - sin_angle * y, sin_angle * x + cos_angle * y, z);
|
||||||
|
if (radial_field.mode() == TpmsAdaptiveMode::SmoothBlend) {
|
||||||
|
// The regular patterns of the two levels around the target of the depth, blended by a smoothstep.
|
||||||
|
auto lattice = [this, x, y](int level) {
|
||||||
|
const double frequency = tpms.surface_frequency * levels.scale(level);
|
||||||
|
return tpms.equation(float(frequency * x), float(frequency * y), float(frequency * z));
|
||||||
|
};
|
||||||
|
if (levels.count == 0)
|
||||||
|
return lattice(0);
|
||||||
|
const double c = std::clamp(levels.level(radial_field.depth(obj)), 0., double(levels.count));
|
||||||
|
const int k = std::min(int(c), levels.count - 1);
|
||||||
|
const double u = c - k;
|
||||||
|
const double w = u * u * (3. - 2. * u);
|
||||||
|
return float((w < 1. ? (1. - w) * lattice(k) : 0.) + (w > 0. ? w * lattice(k + 1) : 0.));
|
||||||
|
}
|
||||||
|
const int axis = radial_field.axis();
|
||||||
|
TpmsRadialField::Radials radials;
|
||||||
|
const size_t count = radial_field.radial(obj, radials);
|
||||||
|
float value = 0.f;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
const auto &[center, t, weight] = radials[i];
|
||||||
|
Vec3d q = tpms.surface_frequency * scale(t) * (obj - center);
|
||||||
|
if (axis >= 0)
|
||||||
|
q[axis] = tpms.interior_frequency * obj[axis];
|
||||||
|
value += weight * tpms.equation(float(cos_angle * q.x() + sin_angle * q.y()), float(cos_angle * q.y() - sin_angle * q.x()), float(q.z()));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline coord_t to_coord(long x) const { return x * rsize; }
|
||||||
|
inline long to_coordr(coord_t x) const { return x / rsize; }
|
||||||
|
inline Point to_Point(const Coord &p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct _RasterTraits<AdaptiveTpmsField>
|
||||||
|
{
|
||||||
|
using ValueType = float;
|
||||||
|
static float get(const AdaptiveTpmsField &sf, size_t row, size_t col) { return sf.get_scalar(Coord(long(row), long(col))); }
|
||||||
|
static size_t rows(const AdaptiveTpmsField &sf) { return sf.to_coordr(sf.size.y()); }
|
||||||
|
static size_t cols(const AdaptiveTpmsField &sf) { return sf.to_coordr(sf.size.x()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Continuous density level of the depth over a layer, in the object frame.
|
||||||
|
struct TpmsLevelField
|
||||||
|
{
|
||||||
|
static constexpr float gsizef = 0.5f;
|
||||||
|
static constexpr float rsizef = 0.05f;
|
||||||
|
const coord_t rsize = scaled(rsizef);
|
||||||
|
const long gsize = std::lround(gsizef / rsizef);
|
||||||
|
|
||||||
|
const TpmsRadialField &field;
|
||||||
|
const DensityLevels &levels;
|
||||||
|
Point size;
|
||||||
|
Point offs;
|
||||||
|
double z;
|
||||||
|
|
||||||
|
TpmsLevelField(const TpmsRadialField &field, const DensityLevels &levels, const BoundingBox &bbox, coordf_t z)
|
||||||
|
: field(field), levels(levels), size(bbox.size()), offs(bbox.min), z(z)
|
||||||
|
{}
|
||||||
|
|
||||||
|
float get_scalar(const Coord &p) const
|
||||||
|
{
|
||||||
|
const Point pt = to_Point(p);
|
||||||
|
return float(levels.level(field.depth(Vec3d(unscaled(pt.x()), unscaled(pt.y()), z))));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline coord_t to_coord(long x) const { return x * rsize; }
|
||||||
|
inline long to_coordr(coord_t x) const { return x / rsize; }
|
||||||
|
inline Point to_Point(const Coord &p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct _RasterTraits<TpmsLevelField>
|
||||||
|
{
|
||||||
|
using ValueType = float;
|
||||||
|
static float get(const TpmsLevelField &sf, size_t row, size_t col) { return sf.get_scalar(Coord(long(row), long(col))); }
|
||||||
|
static size_t rows(const TpmsLevelField &sf) { return sf.to_coordr(sf.size.y()); }
|
||||||
|
static size_t cols(const TpmsLevelField &sf) { return sf.to_coordr(sf.size.x()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace marchsq
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
Polylines make_adaptive_tpms(const AdaptiveTpms &tpms, const TpmsRadialField &field, BoundingBox bbox,
|
||||||
|
coordf_t z, coordf_t layer_height, coordf_t spacing, float angle)
|
||||||
|
{
|
||||||
|
// A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region.
|
||||||
|
const coord_t cell = scaled(marchsq::AdaptiveTpmsField::gsizef);
|
||||||
|
bbox.offset(cell);
|
||||||
|
bbox.merge(align_to_grid(bbox.min, Point(cell, cell)));
|
||||||
|
const marchsq::AdaptiveTpmsField raster(tpms, field, bbox, z - 0.5 * layer_height, angle);
|
||||||
|
const std::vector<marchsq::Ring> rings = marchsq::execute_with_policy(ex_tbb, raster, 0.f, {raster.gsize, raster.gsize});
|
||||||
|
|
||||||
|
// Loops narrower than two lines print as blobs.
|
||||||
|
const double min_loop_length = scaled(2. * PI * spacing);
|
||||||
|
Polylines polylines;
|
||||||
|
polylines.reserve(rings.size());
|
||||||
|
for (const marchsq::Ring &ring : rings) {
|
||||||
|
Polyline polyline;
|
||||||
|
polyline.points.reserve(ring.size() + 1);
|
||||||
|
for (const marchsq::Coord &crd : ring)
|
||||||
|
polyline.points.emplace_back(raster.to_Point(crd));
|
||||||
|
polyline.points.push_back(polyline.points.front());
|
||||||
|
polyline.simplify(SCALED_SPARSE_INFILL_RESOLUTION);
|
||||||
|
if (polyline.length() >= min_loop_length)
|
||||||
|
polylines.push_back(std::move(polyline));
|
||||||
|
}
|
||||||
|
return polylines;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TpmsShell> make_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z,
|
||||||
|
float surface_density, float interior_density, TpmsAdaptiveGradient gradient)
|
||||||
|
{
|
||||||
|
const DensityLevels levels(interior_density / surface_density, ShellRatio, gradient);
|
||||||
|
if (levels.count == 0)
|
||||||
|
return {{surface_density, {expolygon}}};
|
||||||
|
// A fixed sampling grid, so that every region of a layer gets the same shells.
|
||||||
|
const coord_t cell = scaled(marchsq::TpmsLevelField::gsizef);
|
||||||
|
BoundingBox bbox = get_extents(expolygon);
|
||||||
|
bbox.offset(cell);
|
||||||
|
bbox.merge(align_to_grid(bbox.min, Point(cell, cell)));
|
||||||
|
const marchsq::TpmsLevelField raster(field, levels, bbox, z);
|
||||||
|
|
||||||
|
// Each level takes the part deeper than the middle between it and the previous one.
|
||||||
|
std::vector<TpmsShell> shells;
|
||||||
|
ExPolygons remaining{expolygon};
|
||||||
|
for (int level = 0; level < levels.count && !remaining.empty(); ++level) {
|
||||||
|
Polygons deeper;
|
||||||
|
for (const marchsq::Ring &ring : marchsq::execute_with_policy(ex_tbb, raster, float(level + 0.5), {raster.gsize, raster.gsize})) {
|
||||||
|
Polygon &polygon = deeper.emplace_back();
|
||||||
|
polygon.points.reserve(ring.size());
|
||||||
|
for (const marchsq::Coord &crd : ring)
|
||||||
|
polygon.points.emplace_back(raster.to_Point(crd));
|
||||||
|
}
|
||||||
|
ExPolygons inner = intersection_ex(union_ex(deeper), remaining);
|
||||||
|
shells.push_back({float(surface_density * levels.scale(level)), diff_ex(remaining, inner)});
|
||||||
|
remaining = std::move(inner);
|
||||||
|
}
|
||||||
|
if (!remaining.empty())
|
||||||
|
shells.push_back({interior_density, std::move(remaining)});
|
||||||
|
return shells;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z, const FillParams ¶ms, coordf_t spacing,
|
||||||
|
const std::function<void(const FillParams &, const ExPolygon &)> &fill_shell)
|
||||||
|
{
|
||||||
|
FillParams shell_params = params;
|
||||||
|
shell_params.tpms_adaptive = TpmsAdaptiveMode::Disabled;
|
||||||
|
for (const TpmsShell &shell : make_tpms_shells(field, expolygon, z, params.density, params.tpms_interior_density, params.tpms_adaptive_gradient)) {
|
||||||
|
shell_params.density = shell.density;
|
||||||
|
for (const ExPolygon &part : offset_ex(shell.expolygons, -float(scale_(0.5 * spacing))))
|
||||||
|
fill_shell(shell_params, part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
#include "FillBase.hpp"
|
||||||
|
#include "../BoundingBox.hpp"
|
||||||
|
#include "../ExPolygon.hpp"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
#include "../Polyline.hpp"
|
||||||
|
#include "../PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Radial coordinate inside the lobes of the bodies of an object, sampled from its slices: 0 at the center of a
|
||||||
|
// lobe, 1 at its surface. Lobes are parts of a body separated by a neck, like two spheres united. In the 2D modes,
|
||||||
|
// every section normal to the axis has its own bodies and lobes, and distances are measured within the section. The
|
||||||
|
// modes following the distance to the surface use the depth of every point instead; Distance warp also the lobes.
|
||||||
|
class TpmsRadialField
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Slice
|
||||||
|
{
|
||||||
|
coordf_t bottom_z;
|
||||||
|
coordf_t top_z;
|
||||||
|
const ExPolygons *expolygons;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Radial
|
||||||
|
{
|
||||||
|
Vec3d center;
|
||||||
|
double t;
|
||||||
|
float weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Slices sorted by z, in the XY coordinates of the fill and the print Z.
|
||||||
|
TpmsRadialField(const std::vector<Slice> &slices, const BoundingBox &bbox, TpmsAdaptiveMode mode,
|
||||||
|
const std::function<void()> &throw_if_canceled);
|
||||||
|
|
||||||
|
// Lobes blended near the sides between them, from a body or from each of the two sections around a point.
|
||||||
|
static constexpr size_t MaxMorph = 4;
|
||||||
|
using Radials = std::array<Radial, 2 * MaxMorph>;
|
||||||
|
|
||||||
|
// Without a body, as when the object is thinner than the grid cells.
|
||||||
|
bool empty() const { return m_bodies.empty(); }
|
||||||
|
|
||||||
|
// Radial coordinates of pt in unscaled coordinates towards the lobe it belongs to, and towards the neighbouring
|
||||||
|
// lobes near the sides between them, with weights summing to 1. In the 2D modes, those of the two sections around
|
||||||
|
// pt. Returns their count.
|
||||||
|
size_t radial(const Vec3d &pt, Radials &out) const;
|
||||||
|
|
||||||
|
// Axis normal to the sections in the 2D modes, -1 in the modes graded in 3D.
|
||||||
|
int axis() const { return m_axis; }
|
||||||
|
TpmsAdaptiveMode mode() const { return m_mode; }
|
||||||
|
|
||||||
|
// In the modes following the distance to the surface: depth relative to the deepest point of the body, from 0 at
|
||||||
|
// the surface to 1.
|
||||||
|
double depth(const Vec3d &pt) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Lobe
|
||||||
|
{
|
||||||
|
Vec3d center;
|
||||||
|
double depth;
|
||||||
|
// Distance from the center to the surface on a latitude-longitude grid of directions.
|
||||||
|
std::vector<float> reach;
|
||||||
|
// Distance warp: mean depth over the ball along each direction, sampled up to the reach.
|
||||||
|
std::vector<float> mean_depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Body
|
||||||
|
{
|
||||||
|
size_t first_lobe;
|
||||||
|
size_t lobes;
|
||||||
|
// Distance from the deepest point to the surface.
|
||||||
|
double depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
double radial(const Lobe &lobe, const Vec3d &pt) const;
|
||||||
|
size_t body_radial(size_t node, const Vec3d &pt, float weight, Radial *out) const;
|
||||||
|
// Offset of pt from a center, within the section in the 2D modes.
|
||||||
|
Vec3d offset(const Vec3d &pt, const Vec3d ¢er) const;
|
||||||
|
int directions() const { return m_axis < 0 ? Polar * Azimuth : Azimuth; }
|
||||||
|
|
||||||
|
// Directions of the reach of a lobe, on a latitude-longitude grid, or a circle in the 2D modes.
|
||||||
|
static constexpr int Polar = 24;
|
||||||
|
static constexpr int Azimuth = 48;
|
||||||
|
|
||||||
|
TpmsAdaptiveMode m_mode;
|
||||||
|
int m_axis;
|
||||||
|
Vec3d m_origin;
|
||||||
|
double m_cell;
|
||||||
|
Vec3i32 m_size;
|
||||||
|
// Nearest body of every grid node.
|
||||||
|
std::vector<int> m_body;
|
||||||
|
std::vector<Body> m_bodies;
|
||||||
|
std::vector<Lobe> m_lobes;
|
||||||
|
// In the 2D modes, the nearest section with a body to every section.
|
||||||
|
std::vector<int> m_section;
|
||||||
|
// In the modes following the distance to the surface, the depth of every grid node.
|
||||||
|
std::vector<float> m_depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
using TpmsRadialFieldPtr = std::unique_ptr<TpmsRadialField>;
|
||||||
|
// A field for every adaptive mode in use, indexed by the mode.
|
||||||
|
using TpmsRadialFields = std::array<TpmsRadialFieldPtr, size_t(TpmsAdaptiveMode::Count)>;
|
||||||
|
|
||||||
|
struct AdaptiveTpms
|
||||||
|
{
|
||||||
|
// Implicit TPMS equation with a period of 2 PI.
|
||||||
|
float (*equation)(float x, float y, float z);
|
||||||
|
// Pattern frequencies at the surface and at the center, in radians per mm.
|
||||||
|
double surface_frequency;
|
||||||
|
double interior_frequency;
|
||||||
|
TpmsAdaptiveGradient gradient;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Infill lines in the fill frame, the object frame rotated by -angle; z is the print_z of the layer.
|
||||||
|
Polylines make_adaptive_tpms(const AdaptiveTpms &tpms, const TpmsRadialField &field, BoundingBox bbox,
|
||||||
|
coordf_t z, coordf_t layer_height, coordf_t spacing, float angle);
|
||||||
|
|
||||||
|
struct TpmsShell
|
||||||
|
{
|
||||||
|
float density;
|
||||||
|
ExPolygons expolygons;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stepped shells: the parts of an expolygon in the object frame at each density, from the surface inwards; z is the
|
||||||
|
// middle of the layer.
|
||||||
|
std::vector<TpmsShell> make_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z,
|
||||||
|
float surface_density, float interior_density, TpmsAdaptiveGradient gradient);
|
||||||
|
|
||||||
|
// Stepped shells: fills every shell with fill_shell at its density, each shrunk by half a line like a filled region,
|
||||||
|
// so the lines connected along the boundaries of two shells don't overlap.
|
||||||
|
void fill_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z, const FillParams ¶ms, coordf_t spacing,
|
||||||
|
const std::function<void(const FillParams &, const ExPolygon &)> &fill_shell);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -14,8 +14,10 @@
|
|||||||
#include "libslic3r/Fill/FillBase.hpp"
|
#include "libslic3r/Fill/FillBase.hpp"
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Point.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "FillTpmsD.hpp"
|
#include "FillTpmsD.hpp"
|
||||||
|
#include "FillTpmsAdaptive.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -23,6 +25,11 @@ static double scaled_floor(double x,double scale){
|
|||||||
return std::floor(x/scale)*scale;
|
return std::floor(x/scale)*scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float schwarz_d(float x, float y, float z)
|
||||||
|
{
|
||||||
|
return std::sin(x) * std::sin(y) * std::sin(z) - std::cos(x) * std::cos(y) * std::cos(z);
|
||||||
|
}
|
||||||
|
|
||||||
static Polylines make_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
|
static Polylines make_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
|
||||||
{
|
{
|
||||||
const double scaleFactor = scale_(line_spacing) / density_adjusted;
|
const double scaleFactor = scale_(line_spacing) / density_adjusted;
|
||||||
@@ -110,31 +117,49 @@ void FillTpmsD::_fill_surface_single(
|
|||||||
ExPolygon expolygon,
|
ExPolygon expolygon,
|
||||||
Polylines &polylines_out)
|
Polylines &polylines_out)
|
||||||
{
|
{
|
||||||
|
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
|
||||||
|
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
|
||||||
|
[&](const FillParams &shell_params, const ExPolygon &shell) {
|
||||||
|
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
|
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
|
||||||
if(std::abs(infill_angle) >= EPSILON)
|
if(std::abs(infill_angle) >= EPSILON)
|
||||||
expolygon.rotate(-infill_angle);
|
expolygon.rotate(-infill_angle);
|
||||||
|
|
||||||
BoundingBox bb = expolygon.contour.bounding_box();
|
Polylines polylines;
|
||||||
// Density adjusted to have a good %of weight.
|
if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
|
||||||
double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline);
|
// Radians per mm of the regular pattern at a density.
|
||||||
// Distance between the gyroid waves in scaled coordinates.
|
auto frequency = [¶ms, this](double density) { return density * DensityAdjust / (params.multiline * this->spacing); };
|
||||||
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
BoundingBox bbox = expolygon.contour.bounding_box();
|
||||||
|
bbox.offset(scale_((params.multiline + 1) * this->spacing));
|
||||||
|
polylines = make_adaptive_tpms({schwarz_d, frequency(params.density), frequency(params.tpms_interior_density), params.tpms_adaptive_gradient},
|
||||||
|
*this->tpms_radial_field, bbox, this->z, params.layer_height, this->spacing, infill_angle);
|
||||||
|
} else {
|
||||||
|
BoundingBox bb = expolygon.contour.bounding_box();
|
||||||
|
// Density adjusted to have a good %of weight.
|
||||||
|
double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline);
|
||||||
|
// Distance between the gyroid waves in scaled coordinates.
|
||||||
|
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
||||||
|
|
||||||
// align bounding box to a multiple of our grid module
|
// align bounding box to a multiple of our grid module
|
||||||
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
|
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
|
||||||
|
|
||||||
// generate pattern
|
// generate pattern
|
||||||
Polylines polylines = make_waves(
|
polylines = make_waves(
|
||||||
scale_(this->z),
|
scale_(this->z),
|
||||||
density_adjusted,
|
density_adjusted,
|
||||||
this->spacing,
|
this->spacing,
|
||||||
ceil(bb.size()(0) / distance) + 1.,
|
ceil(bb.size()(0) / distance) + 1.,
|
||||||
ceil(bb.size()(1) / distance) + 1.);
|
ceil(bb.size()(1) / distance) + 1.);
|
||||||
|
|
||||||
|
// shift the polyline to the grid origin
|
||||||
|
for (Polyline &pl : polylines)
|
||||||
|
pl.translate(bb.min);
|
||||||
|
}
|
||||||
|
|
||||||
// shift the polyline to the grid origin
|
|
||||||
for (Polyline &pl : polylines)
|
|
||||||
pl.translate(bb.min);
|
|
||||||
|
|
||||||
// Apply multiline offset if needed
|
// Apply multiline offset if needed
|
||||||
multiline_fill(polylines, params, spacing);
|
multiline_fill(polylines, params, spacing);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "libslic3r/Fill/FillBase.hpp"
|
#include "libslic3r/Fill/FillBase.hpp"
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
#include "FillTpmsFK.hpp"
|
#include "FillTpmsFK.hpp"
|
||||||
|
#include "FillTpmsAdaptive.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -17,6 +18,18 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Fischer - Koch S equation:
|
||||||
|
// cos(2x)sin(y)cos(z) + cos(2y)sin(z)cos(x) + cos(2z)sin(x)cos(y) = 0
|
||||||
|
static float fischer_koch(float x, float y, float z)
|
||||||
|
{
|
||||||
|
return cosf(2 * x) * sinf(y) * cosf(z) + cosf(2 * y) * sinf(z) * cosf(x) + cosf(2 * z) * sinf(x) * cosf(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
namespace marchsq {
|
namespace marchsq {
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
@@ -42,16 +55,7 @@ struct ScalarField
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
// Get the scalar field value at x,y,z in coordf_t coordinates.
|
// Get the scalar field value at x,y,z in coordf_t coordinates.
|
||||||
float get_scalar(coordf_t x, coordf_t y, coordf_t z) const
|
float get_scalar(coordf_t x, coordf_t y, coordf_t z) const { return fischer_koch(freq * x, freq * y, freq * z); }
|
||||||
{
|
|
||||||
const float fx = freq * x;
|
|
||||||
const float fy = freq * y;
|
|
||||||
const float fz = freq * z;
|
|
||||||
|
|
||||||
// Fischer - Koch S equation:
|
|
||||||
// cos(2x)sin(y)cos(z) + cos(2y)sin(z)cos(x) + cos(2z)sin(x)cos(y) = 0
|
|
||||||
return cosf(2 * fx) * sinf(fy) * cosf(fz) + cosf(2 * fy) * sinf(fz) * cosf(fx) + cosf(2 * fz) * sinf(fx) * cosf(fy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the scalar field value at a Coord for the current z value.
|
// Get the scalar field value at a Coord for the current z value.
|
||||||
float get_scalar(Coord p) const
|
float get_scalar(Coord p) const
|
||||||
@@ -128,20 +132,34 @@ void FillTpmsFK::_fill_surface_single(const FillParams& params,
|
|||||||
ExPolygon expolygon,
|
ExPolygon expolygon,
|
||||||
Polylines& polylines_out)
|
Polylines& polylines_out)
|
||||||
{
|
{
|
||||||
|
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
|
||||||
|
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
|
||||||
|
[&](const FillParams &shell_params, const ExPolygon &shell) {
|
||||||
|
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto infill_angle = float(this->angle + (CorrectionAngle * 2 * M_PI) / 360.);
|
auto infill_angle = float(this->angle + (CorrectionAngle * 2 * M_PI) / 360.);
|
||||||
if (std::abs(infill_angle) >= EPSILON)
|
if (std::abs(infill_angle) >= EPSILON)
|
||||||
expolygon.rotate(-infill_angle);
|
expolygon.rotate(-infill_angle);
|
||||||
|
|
||||||
float density_factor = std::min(0.9f, params.density);
|
|
||||||
// Density (field period) adjusted to have a good %of weight.
|
// Density (field period) adjusted to have a good %of weight.
|
||||||
const float vari_T = 4.18f * spacing * params.multiline / density_factor;
|
auto period = [¶ms, this](float density) { return 4.18f * spacing * params.multiline / std::min(0.9f, density); };
|
||||||
|
|
||||||
BoundingBox bbox = expolygon.contour.bounding_box();
|
BoundingBox bbox = expolygon.contour.bounding_box();
|
||||||
// Enlarge the bounding box by the multi-line width to avoid artifacts at the edges.
|
// Enlarge the bounding box by the multi-line width to avoid artifacts at the edges.
|
||||||
bbox.offset(scale_((params.multiline + 1) * spacing));
|
bbox.offset(scale_((params.multiline + 1) * spacing));
|
||||||
marchsq::ScalarField sf = marchsq::ScalarField(bbox, this->z, vari_T);
|
Polylines polylines;
|
||||||
// Get simplified lines using coarse tolerance of 0.1mm (this is infill).
|
if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
|
||||||
Polylines polylines = marchsq::get_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
|
polylines = make_adaptive_tpms({fischer_koch, 2. * PI / period(params.density), 2. * PI / period(params.tpms_interior_density),
|
||||||
|
params.tpms_adaptive_gradient},
|
||||||
|
*this->tpms_radial_field, bbox, this->z, params.layer_height, spacing, infill_angle);
|
||||||
|
} else {
|
||||||
|
marchsq::ScalarField sf = marchsq::ScalarField(bbox, this->z, period(params.density));
|
||||||
|
// Get simplified lines using coarse tolerance of 0.1mm (this is infill).
|
||||||
|
polylines = marchsq::get_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply multiline offset if needed
|
// Apply multiline offset if needed
|
||||||
multiline_fill(polylines, params, spacing);
|
multiline_fill(polylines, params, spacing);
|
||||||
|
|||||||
@@ -314,8 +314,13 @@ void AMFParserContext::startElement(const char *name, const char **atts)
|
|||||||
case 2:
|
case 2:
|
||||||
if (strcmp(name, "metadata") == 0) {
|
if (strcmp(name, "metadata") == 0) {
|
||||||
if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) {
|
if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) {
|
||||||
m_value[0] = get_attribute(atts, "type");
|
const char *type = get_attribute(atts, "type");
|
||||||
node_type_new = NODE_TYPE_METADATA;
|
if (type == nullptr)
|
||||||
|
this->stop();
|
||||||
|
else {
|
||||||
|
m_value[0] = type;
|
||||||
|
node_type_new = NODE_TYPE_METADATA;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}/* else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT)
|
}/* else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT)
|
||||||
node_type_new = NODE_TYPE_LAYER_CONFIG;*/
|
node_type_new = NODE_TYPE_LAYER_CONFIG;*/
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "Polyline.hpp"
|
#include "Polyline.hpp"
|
||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
|
#include "ConnectedBodies.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "enum_bitmask.hpp"
|
#include "enum_bitmask.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
@@ -2590,6 +2591,156 @@ WipeTowerType GCode::wipe_tower_type()
|
|||||||
return WipeTowerType::Type2;
|
return WipeTowerType::Type2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Numbers the object instances and the connected bodies of the instances of several, for the processor to find those an
|
||||||
|
// extrusion lies in.
|
||||||
|
static void set_mass_locator(GCodeProcessor &processor, const Print &print)
|
||||||
|
{
|
||||||
|
struct Object
|
||||||
|
{
|
||||||
|
const PrintObject *object;
|
||||||
|
int first_instance;
|
||||||
|
// No bodies for an object of one.
|
||||||
|
size_t bodies_count;
|
||||||
|
int first_body;
|
||||||
|
std::vector<coordf_t> print_zs;
|
||||||
|
// Per layer, the body of each island and a locator whose boxes are widened for walls reaching past them.
|
||||||
|
std::vector<std::vector<size_t>> bodies;
|
||||||
|
std::vector<IslandLocator> islands;
|
||||||
|
// Per instance, whether its widened box reaches another's, so that the box of an island proves nothing.
|
||||||
|
std::vector<bool> crowded;
|
||||||
|
};
|
||||||
|
std::vector<Object> objects;
|
||||||
|
std::vector<GCodeProcessorResult::ObjectMass> object_masses;
|
||||||
|
int bodies_total = 0;
|
||||||
|
for (const PrintObject *object : print.objects()) {
|
||||||
|
const auto layers = object->layers();
|
||||||
|
if (layers.empty())
|
||||||
|
continue;
|
||||||
|
// Bodies for assemblies only, as the Prepare tab counts them: those separated infills found, if it needed them.
|
||||||
|
const ModelVolumePtrs &volumes = object->model_object()->volumes;
|
||||||
|
const bool assembly = std::count_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return v->is_model_part(); }) > 1 ||
|
||||||
|
std::any_of(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return v->is_negative_volume(); });
|
||||||
|
size_t count = 0;
|
||||||
|
std::vector<std::vector<size_t>> bodies;
|
||||||
|
if (assembly) {
|
||||||
|
count = object->separated_body_bboxes().size();
|
||||||
|
if (count > 0 && std::all_of(layers.begin(), layers.end(), [](const Layer *l) { return l->lslices_separated_component_ids.size() == l->lslices.size(); }))
|
||||||
|
for (const Layer *layer : layers)
|
||||||
|
bodies.emplace_back(layer->lslices_separated_component_ids);
|
||||||
|
else {
|
||||||
|
std::vector<const ExPolygons *> islands;
|
||||||
|
for (const Layer *layer : layers)
|
||||||
|
islands.emplace_back(&layer->lslices);
|
||||||
|
bodies = connected_bodies(islands, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count < 2) {
|
||||||
|
count = 0;
|
||||||
|
bodies.assign(layers.size(), {});
|
||||||
|
}
|
||||||
|
Object &o = objects.emplace_back(Object{ object, int(object_masses.size()), count, bodies_total, {}, std::move(bodies), {}, {} });
|
||||||
|
object_masses.resize(object_masses.size() + object->instances().size());
|
||||||
|
for (size_t instance = 0; instance < object->instances().size(); ++instance)
|
||||||
|
object_masses[o.first_instance + instance].assembly = assembly;
|
||||||
|
bodies_total += int(count * object->instances().size());
|
||||||
|
for (const Layer *layer : layers) {
|
||||||
|
o.print_zs.emplace_back(layer->print_z);
|
||||||
|
o.islands.emplace_back(layer->lslices, scaled<coord_t>(1.));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (objects.empty())
|
||||||
|
return;
|
||||||
|
std::vector<BoundingBox> boxes;
|
||||||
|
for (const Object &o : objects) {
|
||||||
|
BoundingBox box;
|
||||||
|
for (const IslandLocator &islands : o.islands)
|
||||||
|
for (const BoundingBox &island : islands.boxes())
|
||||||
|
box.merge(island);
|
||||||
|
for (const PrintInstance &instance : o.object->instances()) {
|
||||||
|
BoundingBox &moved = boxes.emplace_back(box);
|
||||||
|
moved.translate(instance.shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Object &o : objects)
|
||||||
|
for (size_t instance = 0; instance < o.object->instances().size(); ++instance) {
|
||||||
|
const size_t i = o.first_instance + instance;
|
||||||
|
o.crowded.emplace_back(false);
|
||||||
|
for (size_t j = 0; j < boxes.size() && !o.crowded.back(); ++j)
|
||||||
|
o.crowded.back() = j != i && boxes[i].overlap(boxes[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Hit
|
||||||
|
{
|
||||||
|
size_t object{ 0 }, instance{ 0 }, layer{ 0 }, island{ 0 };
|
||||||
|
};
|
||||||
|
auto locate = [objects = std::move(objects), footprints = std::move(boxes),
|
||||||
|
last = std::optional<Hit>()](const Vec3d &point, bool support) mutable -> GCodeProcessor::MassLocation {
|
||||||
|
// Supports stand below and around their object: the instance whose footprint holds the point, the one whose center
|
||||||
|
// is nearest among several, else the nearest footprint.
|
||||||
|
if (support) {
|
||||||
|
const Point p(scaled(point.x()), scaled(point.y()));
|
||||||
|
int found = -1;
|
||||||
|
bool inside = false;
|
||||||
|
double best = std::numeric_limits<double>::max();
|
||||||
|
for (size_t i = 0; i < footprints.size(); ++i) {
|
||||||
|
const BoundingBox &box = footprints[i];
|
||||||
|
const double gap = Point((box.min - p).cwiseMax(p - box.max).cwiseMax(0)).cast<double>().squaredNorm();
|
||||||
|
const bool in = gap == 0.;
|
||||||
|
const double d = in ? (box.center() - p).cast<double>().squaredNorm() : gap;
|
||||||
|
if ((in && !inside) || (in == inside && d < best)) {
|
||||||
|
found = int(i);
|
||||||
|
inside = in;
|
||||||
|
best = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { found, -1 };
|
||||||
|
}
|
||||||
|
constexpr double z_tolerance = 0.002;
|
||||||
|
const auto local = [&point, &objects](size_t object, size_t instance) {
|
||||||
|
return Point(Point(scaled(point.x()), scaled(point.y())) - objects[object].object->instances()[instance].shift);
|
||||||
|
};
|
||||||
|
const auto location = [&objects, &last](const Hit &hit) {
|
||||||
|
last = hit;
|
||||||
|
const Object &o = objects[hit.object];
|
||||||
|
return GCodeProcessor::MassLocation{ o.first_instance + int(hit.instance),
|
||||||
|
o.bodies_count == 0 ? -1 : o.first_body + int(hit.instance * o.bodies_count + o.bodies[hit.layer][hit.island]) };
|
||||||
|
};
|
||||||
|
// A point lies on the first layer at or above it, as spiral vase rises through each layer.
|
||||||
|
// Extrusions mostly follow each other on one island.
|
||||||
|
if (last) {
|
||||||
|
const Object &o = objects[last->object];
|
||||||
|
if (point.z() <= o.print_zs[last->layer] + z_tolerance &&
|
||||||
|
(last->layer == 0 || point.z() > o.print_zs[last->layer - 1] + z_tolerance) &&
|
||||||
|
o.islands[last->layer].holds(last->island, local(last->object, last->instance), o.crowded[last->instance]))
|
||||||
|
return location(*last);
|
||||||
|
}
|
||||||
|
// Outside the islands of instances crowding each other, the nearest outline.
|
||||||
|
std::optional<Hit> nearest;
|
||||||
|
double distance = std::numeric_limits<double>::max();
|
||||||
|
for (size_t object = 0; object < objects.size(); ++object) {
|
||||||
|
const Object &o = objects[object];
|
||||||
|
const auto z = std::lower_bound(o.print_zs.begin(), o.print_zs.end(), point.z() - z_tolerance);
|
||||||
|
if (z == o.print_zs.end())
|
||||||
|
continue;
|
||||||
|
const size_t layer = size_t(z - o.print_zs.begin());
|
||||||
|
for (size_t instance = 0; instance < o.object->instances().size(); ++instance) {
|
||||||
|
const auto [island, d] = o.islands[layer].find(local(object, instance), o.crowded[instance]);
|
||||||
|
if (island < 0)
|
||||||
|
continue;
|
||||||
|
const Hit hit{ object, instance, layer, size_t(island) };
|
||||||
|
if (d == 0. || !o.crowded[instance])
|
||||||
|
return location(hit);
|
||||||
|
if (d < distance) {
|
||||||
|
distance = d;
|
||||||
|
nearest = hit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest ? location(*nearest) : GCodeProcessor::MassLocation{};
|
||||||
|
};
|
||||||
|
processor.set_mass_locator(std::move(locate), std::move(object_masses));
|
||||||
|
}
|
||||||
|
|
||||||
void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
|
void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||||
{
|
{
|
||||||
PROFILE_CLEAR();
|
PROFILE_CLEAR();
|
||||||
@@ -3112,6 +3263,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
// modifies m_silent_time_estimator_enabled
|
// modifies m_silent_time_estimator_enabled
|
||||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
|
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
|
||||||
print.get_layered_nozzle_group_result());
|
print.get_layered_nozzle_group_result());
|
||||||
|
set_mass_locator(m_processor, print);
|
||||||
const bool is_bbl_printers = print.is_BBL_printer();
|
const bool is_bbl_printers = print.is_BBL_printer();
|
||||||
const bool skip_config_block = print.config().gcode_skip_config_block;
|
const bool skip_config_block = print.config().gcode_skip_config_block;
|
||||||
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
|
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f;
|
|||||||
static const size_t MIN_EXTRUDERS_COUNT = 5;
|
static const size_t MIN_EXTRUDERS_COUNT = 5;
|
||||||
static const float DEFAULT_FILAMENT_DIAMETER = 1.75f;
|
static const float DEFAULT_FILAMENT_DIAMETER = 1.75f;
|
||||||
static const int DEFAULT_FILAMENT_HRC = 0;
|
static const int DEFAULT_FILAMENT_HRC = 0;
|
||||||
static const float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|
||||||
static const float DEFAULT_FILAMENT_COST = 29.99f;
|
static const float DEFAULT_FILAMENT_COST = 29.99f;
|
||||||
static const int DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE = 0;
|
static const int DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE = 0;
|
||||||
static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
|
static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
|
||||||
@@ -2604,6 +2603,10 @@ void GCodeProcessorResult::reset() {
|
|||||||
lock();
|
lock();
|
||||||
|
|
||||||
moves.clear();
|
moves.clear();
|
||||||
|
plate_mass = {};
|
||||||
|
object_masses.clear();
|
||||||
|
body_masses.clear();
|
||||||
|
support_masses.clear();
|
||||||
lines_ends.clear();
|
lines_ends.clear();
|
||||||
printable_area = Pointfs();
|
printable_area = Pointfs();
|
||||||
//BBS: add bed exclude area
|
//BBS: add bed exclude area
|
||||||
@@ -3702,6 +3705,7 @@ void GCodeProcessor::reset()
|
|||||||
m_g1_line_id = 0;
|
m_g1_line_id = 0;
|
||||||
m_layer_id = 0;
|
m_layer_id = 0;
|
||||||
m_cp_color.reset();
|
m_cp_color.reset();
|
||||||
|
m_mass_locator = nullptr;
|
||||||
|
|
||||||
m_producer = EProducer::Unknown;
|
m_producer = EProducer::Unknown;
|
||||||
|
|
||||||
@@ -3841,6 +3845,7 @@ void GCodeProcessor::process_buffer(const std::string &buffer)
|
|||||||
void GCodeProcessor::finalize(bool post_process)
|
void GCodeProcessor::finalize(bool post_process)
|
||||||
{
|
{
|
||||||
m_result.z_offset = m_z_offset;
|
m_result.z_offset = m_z_offset;
|
||||||
|
finalize_object_masses();
|
||||||
|
|
||||||
// update width/height of wipe moves
|
// update width/height of wipe moves
|
||||||
for (GCodeProcessorResult::MoveVertex& move : m_result.moves) {
|
for (GCodeProcessorResult::MoveVertex& move : m_result.moves) {
|
||||||
@@ -5469,6 +5474,9 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
|||||||
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset);
|
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == EMoveType::Extrude)
|
||||||
|
add_object_mass(filament_id, area_filament_cross_section * delta_pos[E]);
|
||||||
|
|
||||||
// store move
|
// store move
|
||||||
store_move_vertex(type);
|
store_move_vertex(type);
|
||||||
}
|
}
|
||||||
@@ -7277,6 +7285,73 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GCodeProcessorResult::ObjectMass::add(const Sum &sum, const BoundingBoxf3 &extent, size_t layer)
|
||||||
|
{
|
||||||
|
box.merge(extent);
|
||||||
|
if (printed_up_to_layer.size() <= layer)
|
||||||
|
printed_up_to_layer.resize(layer + 1);
|
||||||
|
printed_up_to_layer[layer].add(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCodeProcessor::add_object_mass(int filament_id, float volume)
|
||||||
|
{
|
||||||
|
// Skirt, prime tower and custom G-code belong to no object.
|
||||||
|
const ExtrusionRole role = m_extrusion_role;
|
||||||
|
if (volume <= 0.f || role == erNone || role == erSkirt || role == erWipeTower || role == erCustom || role == erMixed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool has_density = size_t(filament_id) < m_result.filament_densities.size() && m_result.filament_densities[filament_id] > 0.f;
|
||||||
|
const double mass = double(volume) * (has_density ? m_result.filament_densities[filament_id] : DEFAULT_FILAMENT_DENSITY);
|
||||||
|
// In the frame of the stored moves, the bead's center half its height below the nozzle, from the move's start to its end.
|
||||||
|
const Vec3d half_height = 0.5 * double(m_height) * Vec3d::UnitZ();
|
||||||
|
const Vec3d offset = Vec3d(m_x_offset, m_y_offset, -m_z_offset) - half_height + m_extruder_offsets[filament_id].cast<double>();
|
||||||
|
const Vec3d start = Vec3d(m_start_position[X], m_start_position[Y], m_start_position[Z]) + offset;
|
||||||
|
const Vec3d end = Vec3d(m_end_position[X], m_end_position[Y], m_end_position[Z]) + offset;
|
||||||
|
// The second moments of a uniform segment.
|
||||||
|
const GCodeProcessorResult::ObjectMass::Sum sum{ mass, double(volume), 0.5 * mass * (start + end),
|
||||||
|
mass / 3. * (start.cwiseProduct(start) + start.cwiseProduct(end) + end.cwiseProduct(end)) };
|
||||||
|
// Of the bead's center line and its height, as its width is only estimated. Merged, as a wall along an axis is flat.
|
||||||
|
BoundingBoxf3 extent;
|
||||||
|
extent.merge(start.cwiseMin(end) - half_height);
|
||||||
|
extent.merge(start.cwiseMax(end) + half_height);
|
||||||
|
const bool part = role != erBrim && !is_support(role);
|
||||||
|
const size_t layer = std::max<unsigned int>(1, m_layer_id) - 1;
|
||||||
|
|
||||||
|
m_result.plate_mass.add(sum, extent, layer);
|
||||||
|
// The brim belongs to the plate alone.
|
||||||
|
if (role == erBrim || !m_mass_locator)
|
||||||
|
return;
|
||||||
|
const auto add = [&sum, &extent, layer](std::vector<GCodeProcessorResult::ObjectMass> &masses, int index) {
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
if (masses.size() <= size_t(index))
|
||||||
|
masses.resize(index + 1);
|
||||||
|
masses[index].add(sum, extent, layer);
|
||||||
|
};
|
||||||
|
// At the nozzle's height, which the layers print at.
|
||||||
|
const MassLocation location = m_mass_locator(0.5 * (start + end) + half_height, !part);
|
||||||
|
if (part) {
|
||||||
|
add(m_result.object_masses, location.object);
|
||||||
|
add(m_result.body_masses, location.body);
|
||||||
|
} else
|
||||||
|
add(m_result.support_masses, location.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCodeProcessor::finalize_object_masses()
|
||||||
|
{
|
||||||
|
const auto accumulate = [](GCodeProcessorResult::ObjectMass &object) {
|
||||||
|
for (size_t i = 1; i < object.printed_up_to_layer.size(); ++i)
|
||||||
|
object.printed_up_to_layer[i].add(object.printed_up_to_layer[i - 1]);
|
||||||
|
};
|
||||||
|
accumulate(m_result.plate_mass);
|
||||||
|
for (GCodeProcessorResult::ObjectMass &object : m_result.object_masses)
|
||||||
|
accumulate(object);
|
||||||
|
for (GCodeProcessorResult::ObjectMass &body : m_result.body_masses)
|
||||||
|
accumulate(body);
|
||||||
|
for (GCodeProcessorResult::ObjectMass &support : m_result.support_masses)
|
||||||
|
accumulate(support);
|
||||||
|
}
|
||||||
|
|
||||||
void GCodeProcessor::set_extrusion_role(ExtrusionRole role)
|
void GCodeProcessor::set_extrusion_role(ExtrusionRole role)
|
||||||
{
|
{
|
||||||
m_used_filaments.process_role_cache(this);
|
m_used_filaments.process_role_cache(this);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "libslic3r/CommonDefs.hpp"
|
#include "libslic3r/CommonDefs.hpp"
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "libslic3r/ArcFitter.hpp"
|
#include "libslic3r/ArcFitter.hpp"
|
||||||
@@ -35,6 +36,9 @@ namespace Slic3r {
|
|||||||
|
|
||||||
class Print;
|
class Print;
|
||||||
|
|
||||||
|
// For a filament whose density is not set, in g/cm³.
|
||||||
|
inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
||||||
|
|
||||||
// slice warnings enum strings
|
// slice warnings enum strings
|
||||||
#define NOZZLE_HRC_CHECKER "the_actual_nozzle_hrc_smaller_than_the_required_nozzle_hrc"
|
#define NOZZLE_HRC_CHECKER "the_actual_nozzle_hrc_smaller_than_the_required_nozzle_hrc"
|
||||||
#define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament"
|
#define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament"
|
||||||
@@ -270,9 +274,44 @@ class Print;
|
|||||||
std::vector<std::string> params; // extra msg info
|
std::vector<std::string> params; // extra msg info
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Material extruded for the plate, one object instance or one connected body of it, for their centers of mass.
|
||||||
|
struct ObjectMass
|
||||||
|
{
|
||||||
|
struct Sum
|
||||||
|
{
|
||||||
|
double mass{ 0. };
|
||||||
|
double volume{ 0. };
|
||||||
|
Vec3d moment{ Vec3d::Zero() };
|
||||||
|
// Of the mass about the origin along each axis, the sums of m x^2, m y^2 and m z^2.
|
||||||
|
Vec3d second{ Vec3d::Zero() };
|
||||||
|
|
||||||
|
void add(const Sum &other)
|
||||||
|
{
|
||||||
|
mass += other.mass;
|
||||||
|
volume += other.volume;
|
||||||
|
moment += other.moment;
|
||||||
|
second += other.second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Everything printed up to each layer id, the plate's with brim, raft and supports, and the box it fills.
|
||||||
|
std::vector<Sum> printed_up_to_layer;
|
||||||
|
BoundingBoxf3 box;
|
||||||
|
// Of an object, whether it is an assembly.
|
||||||
|
bool assembly{ false };
|
||||||
|
|
||||||
|
Sum total() const { return printed_up_to_layer.empty() ? Sum{} : printed_up_to_layer.back(); }
|
||||||
|
void add(const Sum &sum, const BoundingBoxf3 &extent, size_t layer);
|
||||||
|
};
|
||||||
|
|
||||||
std::string filename;
|
std::string filename;
|
||||||
unsigned int id;
|
unsigned int id;
|
||||||
std::vector<MoveVertex> moves;
|
std::vector<MoveVertex> moves;
|
||||||
|
ObjectMass plate_mass;
|
||||||
|
// One per object instance, and one per connected body of the instances of several, when the sliced objects were at hand.
|
||||||
|
std::vector<ObjectMass> object_masses;
|
||||||
|
std::vector<ObjectMass> body_masses;
|
||||||
|
// One per object instance, of its supports and raft.
|
||||||
|
std::vector<ObjectMass> support_masses;
|
||||||
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
|
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
|
||||||
std::vector<size_t> lines_ends;
|
std::vector<size_t> lines_ends;
|
||||||
Pointfs printable_area;
|
Pointfs printable_area;
|
||||||
@@ -360,6 +399,10 @@ class Print;
|
|||||||
filename = std::forward<Other>(other).filename;
|
filename = std::forward<Other>(other).filename;
|
||||||
id = std::forward<Other>(other).id;
|
id = std::forward<Other>(other).id;
|
||||||
moves = std::forward<Other>(other).moves;
|
moves = std::forward<Other>(other).moves;
|
||||||
|
plate_mass = std::forward<Other>(other).plate_mass;
|
||||||
|
object_masses = std::forward<Other>(other).object_masses;
|
||||||
|
body_masses = std::forward<Other>(other).body_masses;
|
||||||
|
support_masses = std::forward<Other>(other).support_masses;
|
||||||
lines_ends = std::forward<Other>(other).lines_ends;
|
lines_ends = std::forward<Other>(other).lines_ends;
|
||||||
printable_area = std::forward<Other>(other).printable_area;
|
printable_area = std::forward<Other>(other).printable_area;
|
||||||
bed_exclude_area = std::forward<Other>(other).bed_exclude_area;
|
bed_exclude_area = std::forward<Other>(other).bed_exclude_area;
|
||||||
@@ -1099,6 +1142,15 @@ class Print;
|
|||||||
};
|
};
|
||||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||||
|
|
||||||
|
// The object instance and the connected body of an instance of several that a point lies in, -1 for none.
|
||||||
|
struct MassLocation
|
||||||
|
{
|
||||||
|
int object{ -1 };
|
||||||
|
int body{ -1 };
|
||||||
|
};
|
||||||
|
// For a support, the object instance only.
|
||||||
|
using MassLocator = std::function<MassLocation(const Vec3d &point, bool support)>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CommandProcessor m_command_processor;
|
CommandProcessor m_command_processor;
|
||||||
GCodeReader m_parser;
|
GCodeReader m_parser;
|
||||||
@@ -1126,6 +1178,7 @@ class Print;
|
|||||||
bool m_skippable{false};
|
bool m_skippable{false};
|
||||||
SkipType m_skippable_type{SkipType::stNone};
|
SkipType m_skippable_type{SkipType::stNone};
|
||||||
int m_object_label_id{-1};
|
int m_object_label_id{-1};
|
||||||
|
MassLocator m_mass_locator;
|
||||||
float m_print_z{0.0f};
|
float m_print_z{0.0f};
|
||||||
std::vector<float> m_remaining_volume;
|
std::vector<float> m_remaining_volume;
|
||||||
ExtruderTemps m_filament_nozzle_temp;
|
ExtruderTemps m_filament_nozzle_temp;
|
||||||
@@ -1280,6 +1333,13 @@ class Print;
|
|||||||
const std::vector<std::set<int>>& unprintable_filament_types );
|
const std::vector<std::set<int>>& unprintable_filament_types );
|
||||||
void apply_config(const PrintConfig& config);
|
void apply_config(const PrintConfig& config);
|
||||||
void set_print(Print* print) { m_print = print; }
|
void set_print(Print* print) { m_print = print; }
|
||||||
|
// Locates extrusions in the objects and bodies it numbers, those objects listed beforehand.
|
||||||
|
void set_mass_locator(MassLocator locator, std::vector<GCodeProcessorResult::ObjectMass> objects)
|
||||||
|
{
|
||||||
|
m_mass_locator = std::move(locator);
|
||||||
|
m_result.support_masses.assign(objects.size(), {});
|
||||||
|
m_result.object_masses = std::move(objects);
|
||||||
|
}
|
||||||
// Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the
|
// Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the
|
||||||
// per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0).
|
// per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0).
|
||||||
void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) {
|
void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) {
|
||||||
@@ -1534,6 +1594,8 @@ class Print;
|
|||||||
|
|
||||||
//BBS: different path_type is only used for arc move
|
//BBS: different path_type is only used for arc move
|
||||||
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move, bool internal_only = false);
|
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move, bool internal_only = false);
|
||||||
|
void add_object_mass(int filament_id, float volume);
|
||||||
|
void finalize_object_masses();
|
||||||
|
|
||||||
void set_extrusion_role(ExtrusionRole role);
|
void set_extrusion_role(ExtrusionRole role);
|
||||||
// Resolve the SKIPPABLE_TYPE payload to a SkipType.
|
// Resolve the SKIPPABLE_TYPE payload to a SkipType.
|
||||||
|
|||||||
@@ -437,10 +437,14 @@ coordf_t Layer::get_sparse_infill_max_void_area()
|
|||||||
double max_void_area = 0.;
|
double max_void_area = 0.;
|
||||||
for (auto layerm : m_regions) {
|
for (auto layerm : m_regions) {
|
||||||
Flow flow = layerm->flow(frInfill);
|
Flow flow = layerm->flow(frInfill);
|
||||||
float density = layerm->region().config().sparse_infill_density;
|
const PrintRegionConfig &config = layerm->region().config();
|
||||||
InfillPattern pattern = layerm->region().config().sparse_infill_pattern;
|
float density = config.sparse_infill_density;
|
||||||
|
InfillPattern pattern = config.sparse_infill_pattern;
|
||||||
if (density == 0.)
|
if (density == 0.)
|
||||||
return -1;
|
return -1;
|
||||||
|
// Orca: the adaptive TPMS infill is as sparse as its interior density.
|
||||||
|
if (density < 100.f && config.tpms_adaptive != TpmsAdaptiveMode::Disabled && is_tpms_adaptive_pattern(pattern))
|
||||||
|
density = std::min(density, std::max(1.f, float(config.tpms_interior_density)));
|
||||||
|
|
||||||
//BBS: rough estimation and need to be optimized
|
//BBS: rough estimation and need to be optimized
|
||||||
double spacing = flow.scaled_spacing() * (100 - density) / density;
|
double spacing = flow.scaled_spacing() * (100 - density) / density;
|
||||||
|
|||||||
@@ -1181,6 +1181,9 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"is_infill_first",
|
"is_infill_first",
|
||||||
"sparse_infill_density",
|
"sparse_infill_density",
|
||||||
"fill_multiline",
|
"fill_multiline",
|
||||||
|
"tpms_adaptive",
|
||||||
|
"tpms_interior_density",
|
||||||
|
"tpms_adaptive_gradient",
|
||||||
"gyroid_optimized",
|
"gyroid_optimized",
|
||||||
"sparse_infill_pattern",
|
"sparse_infill_pattern",
|
||||||
"sparse_infill_smooth_factor",
|
"sparse_infill_smooth_factor",
|
||||||
@@ -2345,7 +2348,7 @@ bool PresetCollection::reset_project_embedded_presets()
|
|||||||
return re_select;
|
return re_select;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time)
|
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id)
|
||||||
{
|
{
|
||||||
lock();
|
lock();
|
||||||
const std::string canonical_name = this->canonical_preset_name(name);
|
const std::string canonical_name = this->canonical_preset_name(name);
|
||||||
@@ -2363,7 +2366,10 @@ void PresetCollection::set_sync_info_and_save(std::string name, std::string sett
|
|||||||
preset2.save_info();
|
preset2.save_info();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preset->setting_id = setting_id;
|
if (!setting_id.empty())
|
||||||
|
preset->setting_id = setting_id;
|
||||||
|
if (!user_id.empty())
|
||||||
|
preset->user_id = user_id;
|
||||||
if (update_time > 0)
|
if (update_time > 0)
|
||||||
preset->updated_time = update_time;
|
preset->updated_time = update_time;
|
||||||
if (preset->sync_info == "update")
|
if (preset->sync_info == "update")
|
||||||
@@ -2653,6 +2659,9 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
|||||||
iter->base_id = based_id;
|
iter->base_id = based_id;
|
||||||
iter->filament_id = cloud_filament_id;
|
iter->filament_id = cloud_filament_id;
|
||||||
update_alias(*iter);
|
update_alias(*iter);
|
||||||
|
// Persist the cloud-assigned identity to disk, mirroring the equal/newer branch
|
||||||
|
// above; otherwise the id stays only in memory and the next launch rewrites it.
|
||||||
|
iter->save_info();
|
||||||
//presets_loaded.emplace_back(*it->second);
|
//presets_loaded.emplace_back(*it->second);
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
|
||||||
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
|
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ public:
|
|||||||
void update_after_user_presets_loaded();
|
void update_after_user_presets_loaded();
|
||||||
//BBS: get user presets
|
//BBS: get user presets
|
||||||
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
|
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
|
||||||
void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time);
|
void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id);
|
||||||
bool need_sync(std::string name, std::string setting_id, long long update_time);
|
bool need_sync(std::string name, std::string setting_id, long long update_time);
|
||||||
|
|
||||||
//BBS: add function to generate differed preset for save
|
//BBS: add function to generate differed preset for save
|
||||||
|
|||||||
@@ -5381,13 +5381,15 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||||||
bool process_multi_extruder = false;
|
bool process_multi_extruder = false;
|
||||||
std::vector<int> filament_variant_index;
|
std::vector<int> filament_variant_index;
|
||||||
size_t extruder_variant_count;
|
size_t extruder_variant_count;
|
||||||
if (!config.option<ConfigOptionInts>("filament_self_index")) {
|
// A config loaded over the full defaults has a one-entry index even when the file has none.
|
||||||
std::vector<int>& filament_self_indice = config.option<ConfigOptionInts>("filament_self_index", true)->values;
|
ConfigOptionInts* filament_self_index_opt = config.option<ConfigOptionInts>("filament_self_index", true);
|
||||||
|
if (filament_self_index_opt->size() < num_filaments) {
|
||||||
|
std::vector<int>& filament_self_indice = filament_self_index_opt->values;
|
||||||
filament_self_indice.resize(num_filaments);
|
filament_self_indice.resize(num_filaments);
|
||||||
for (int index = 0; index < num_filaments; index++)
|
for (int index = 0; index < num_filaments; index++)
|
||||||
filament_self_indice[index] = index + 1;
|
filament_self_indice[index] = index + 1;
|
||||||
}
|
}
|
||||||
std::vector<int> filament_self_indice = std::move(config.option<ConfigOptionInts>("filament_self_index")->values);
|
std::vector<int> filament_self_indice = std::move(filament_self_index_opt->values);
|
||||||
// ORCA: Initialize filament_extruder_variant for backward compatibility with old 3mf files
|
// ORCA: Initialize filament_extruder_variant for backward compatibility with old 3mf files
|
||||||
// that don't have this option saved or have it with default single-element value
|
// that don't have this option saved or have it with default single-element value
|
||||||
ConfigOptionStrings* filament_extruder_variant_opt = config.option<ConfigOptionStrings>("filament_extruder_variant");
|
ConfigOptionStrings* filament_extruder_variant_opt = config.option<ConfigOptionStrings>("filament_extruder_variant");
|
||||||
|
|||||||
@@ -5340,6 +5340,31 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Print::reload_gcode_moves(GCodeProcessorResult* result) const
|
||||||
|
{
|
||||||
|
GCodeProcessor processor;
|
||||||
|
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
|
||||||
|
const Vec3d origin = this->get_plate_origin();
|
||||||
|
processor.set_xy_offset(origin(0), origin(1));
|
||||||
|
// Estimate the per-move times with the same nozzle-grouping slot context as the export.
|
||||||
|
if (result->nozzle_group_result)
|
||||||
|
processor.initialize_from_context(result->nozzle_group_result);
|
||||||
|
try {
|
||||||
|
processor.process_file(result->filename);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
// The edited file is what gets printed, so failing to preview it must not fail the slice.
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": cannot re-read the G-code file " << result->filename << ": " << ex.what();
|
||||||
|
std::lock_guard<std::mutex> lock(result->result_mutex);
|
||||||
|
result->lines_ends.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GCodeProcessorResult& reloaded = processor.result();
|
||||||
|
std::lock_guard<std::mutex> lock(result->result_mutex);
|
||||||
|
result->moves = std::move(reloaded.moves);
|
||||||
|
result->lines_ends = std::move(reloaded.lines_ends);
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
|
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
|
||||||
{
|
{
|
||||||
if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty())
|
if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty())
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
#include "Fill/FillAdaptive.hpp"
|
#include "Fill/FillAdaptive.hpp"
|
||||||
#include "Fill/FillLightning.hpp"
|
#include "Fill/FillLightning.hpp"
|
||||||
|
#include "Fill/FillTpmsAdaptive.hpp"
|
||||||
|
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
#include "ExtrusionEntityCollection.hpp"
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
@@ -406,6 +407,7 @@ public:
|
|||||||
double max_z() const { return m_max_z; }
|
double max_z() const { return m_max_z; }
|
||||||
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
|
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
|
||||||
const Point& center_offset() const { return m_center_offset; }
|
const Point& center_offset() const { return m_center_offset; }
|
||||||
|
const TpmsRadialField* tpms_radial_field(TpmsAdaptiveMode mode) const { return m_tpms_radial_fields[size_t(mode)].get(); }
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
void generate_support_preview();
|
void generate_support_preview();
|
||||||
@@ -650,6 +652,7 @@ private:
|
|||||||
FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
|
FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
|
||||||
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
|
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
|
||||||
FillLightning::GeneratorPtr prepare_lightning_infill_data();
|
FillLightning::GeneratorPtr prepare_lightning_infill_data();
|
||||||
|
TpmsRadialFields prepare_tpms_radial_fields() const;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
SupportNecessaryType is_support_necessary();
|
SupportNecessaryType is_support_necessary();
|
||||||
@@ -700,6 +703,7 @@ private:
|
|||||||
FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
|
FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
|
||||||
std::vector<BoundingBox> m_separated_body_bboxes;
|
std::vector<BoundingBox> m_separated_body_bboxes;
|
||||||
FillLightning::GeneratorPtr m_lightning_generator;
|
FillLightning::GeneratorPtr m_lightning_generator;
|
||||||
|
TpmsRadialFields m_tpms_radial_fields;
|
||||||
|
|
||||||
std::vector < VolumeSlices > firstLayerObjSliceByVolume;
|
std::vector < VolumeSlices > firstLayerObjSliceByVolume;
|
||||||
std::vector<groupedVolumeSlices> firstLayerObjSliceByGroups;
|
std::vector<groupedVolumeSlices> firstLayerObjSliceByGroups;
|
||||||
@@ -1287,6 +1291,11 @@ public:
|
|||||||
void set_gcode_file_ready();
|
void set_gcode_file_ready();
|
||||||
void set_gcode_file_invalidated();
|
void set_gcode_file_invalidated();
|
||||||
void export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
|
void export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
|
||||||
|
// Re-reads the moves and line offsets of `result` from its G-code file after the file was rewritten in
|
||||||
|
// place (post-processing scripts or plugins), so the preview and its G-code window follow the file on
|
||||||
|
// disk. Everything else in `result` was computed while slicing and is kept. If the file cannot be
|
||||||
|
// re-read, the moves are kept and the line offsets are cleared, which hides the G-code window.
|
||||||
|
void reload_gcode_moves(GCodeProcessorResult* result) const;
|
||||||
//BBS: add modify_count logic
|
//BBS: add modify_count logic
|
||||||
int get_modified_count() const {return m_modified_count;}
|
int get_modified_count() const {return m_modified_count;}
|
||||||
//BBS: add status for whether support used
|
//BBS: add status for whether support used
|
||||||
|
|||||||
@@ -364,6 +364,26 @@ static t_config_enum_values s_keys_map_SurfaceFillOrder{
|
|||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SurfaceFillOrder)
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SurfaceFillOrder)
|
||||||
|
|
||||||
|
//Orca
|
||||||
|
static t_config_enum_values s_keys_map_TpmsAdaptiveMode{
|
||||||
|
{ "disabled", int(TpmsAdaptiveMode::Disabled) },
|
||||||
|
{ "distance_warp", int(TpmsAdaptiveMode::DistanceWarp) },
|
||||||
|
{ "smooth_blend", int(TpmsAdaptiveMode::SmoothBlend) },
|
||||||
|
{ "stepped_shells", int(TpmsAdaptiveMode::SteppedShells) },
|
||||||
|
{ "lobes", int(TpmsAdaptiveMode::Lobes) },
|
||||||
|
{ "normal_z", int(TpmsAdaptiveMode::NormalZ) },
|
||||||
|
{ "normal_y", int(TpmsAdaptiveMode::NormalY) },
|
||||||
|
{ "normal_x", int(TpmsAdaptiveMode::NormalX) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TpmsAdaptiveMode)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_TpmsAdaptiveGradient{
|
||||||
|
{ "linear", int(TpmsAdaptiveGradient::Linear) },
|
||||||
|
{ "quadratic", int(TpmsAdaptiveGradient::Quadratic) },
|
||||||
|
{ "exponential", int(TpmsAdaptiveGradient::Exponential) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TpmsAdaptiveGradient)
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
static t_config_enum_values s_keys_map_PrintSequence {
|
static t_config_enum_values s_keys_map_PrintSequence {
|
||||||
{ "by layer", int(PrintSequence::ByLayer) },
|
{ "by layer", int(PrintSequence::ByLayer) },
|
||||||
@@ -3620,6 +3640,73 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->max = 10; // Maximum number of lines for infill pattern
|
def->max = 10; // Maximum number of lines for infill pattern
|
||||||
def->set_default_value(new ConfigOptionInt(1));
|
def->set_default_value(new ConfigOptionInt(1));
|
||||||
|
|
||||||
|
def = this->add("tpms_adaptive", coEnum);
|
||||||
|
def->label = L("Adaptive density (experimental)");
|
||||||
|
def->category = L("Strength");
|
||||||
|
def->tooltip = L("Grades the Gyroid and TPMS infill inside the object: its cells grow from the surface of the "
|
||||||
|
"object towards its center. The sparse infill density is used at the surface and the interior "
|
||||||
|
"density at the center.\n"
|
||||||
|
"Distance warp, Smooth blend and Stepped shells follow the distance to the nearest surface, "
|
||||||
|
"including the top and bottom, with the interior density at the point farthest from it:\n"
|
||||||
|
" - Distance warp: one continuous pattern, stretched and sheared where the distance changes "
|
||||||
|
"across directions, as in plates and long parts.\n"
|
||||||
|
" - Smooth blend: the patterns of neighbouring densities blended into each other, with small "
|
||||||
|
"loops where they meet.\n"
|
||||||
|
" - Stepped shells: shells of the regular pattern at densities about 1.5 times apart, their "
|
||||||
|
"lines joined along the shell boundaries.\n"
|
||||||
|
" - Lobes: follows the 3D shape of the object, including its top and bottom. Every lobe, a part "
|
||||||
|
"joined to the rest by a narrower neck, is graded towards its own center.\n"
|
||||||
|
" - Normal Z, Y or X: follows the sections of the object normal to that axis, so the density "
|
||||||
|
"does not change along it.");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<TpmsAdaptiveMode>::get_enum_values();
|
||||||
|
def->enum_values.push_back("disabled");
|
||||||
|
def->enum_values.push_back("distance_warp");
|
||||||
|
def->enum_values.push_back("smooth_blend");
|
||||||
|
def->enum_values.push_back("stepped_shells");
|
||||||
|
def->enum_values.push_back("lobes");
|
||||||
|
def->enum_values.push_back("normal_z");
|
||||||
|
def->enum_values.push_back("normal_y");
|
||||||
|
def->enum_values.push_back("normal_x");
|
||||||
|
def->enum_labels.push_back(L("Disabled"));
|
||||||
|
def->enum_labels.push_back(L("Distance warp"));
|
||||||
|
def->enum_labels.push_back(L("Smooth blend"));
|
||||||
|
def->enum_labels.push_back(L("Stepped shells"));
|
||||||
|
def->enum_labels.push_back(L("Lobes"));
|
||||||
|
def->enum_labels.push_back(L("Normal Z"));
|
||||||
|
def->enum_labels.push_back(L("Normal Y"));
|
||||||
|
def->enum_labels.push_back(L("Normal X"));
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionEnum<TpmsAdaptiveMode>(TpmsAdaptiveMode::Disabled));
|
||||||
|
|
||||||
|
def = this->add("tpms_interior_density", coPercent);
|
||||||
|
def->label = L("Interior density");
|
||||||
|
def->category = L("Strength");
|
||||||
|
def->tooltip = L("Density of the adaptive infill at the center of the object.");
|
||||||
|
def->sidetext = "%";
|
||||||
|
def->min = 1;
|
||||||
|
def->max = 100;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionPercent(5));
|
||||||
|
|
||||||
|
def = this->add("tpms_adaptive_gradient", coEnum);
|
||||||
|
def->label = L("Adaptive gradient");
|
||||||
|
def->category = L("Strength");
|
||||||
|
def->tooltip = L("How the density changes from the surface to the center of the object.\n"
|
||||||
|
"Linear: the density changes at a constant rate.\n"
|
||||||
|
"Quadratic: the density stays close to the sparse infill density near the surface and "
|
||||||
|
"changes faster towards the center.\n"
|
||||||
|
"Exponential: the density changes quickly just below the surface and levels off towards "
|
||||||
|
"the center.");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<TpmsAdaptiveGradient>::get_enum_values();
|
||||||
|
def->enum_values.push_back("linear");
|
||||||
|
def->enum_values.push_back("quadratic");
|
||||||
|
def->enum_values.push_back("exponential");
|
||||||
|
def->enum_labels.push_back(L("Linear"));
|
||||||
|
def->enum_labels.push_back(L("Quadratic"));
|
||||||
|
def->enum_labels.push_back(L("Exponential"));
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionEnum<TpmsAdaptiveGradient>(TpmsAdaptiveGradient::Linear));
|
||||||
|
|
||||||
// Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z
|
// Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z
|
||||||
// (vertical) axis at low infill density to shorten the effective column length under
|
// (vertical) axis at low infill density to shorten the effective column length under
|
||||||
// Z-axis compression. Filament use at the same `sparse_infill_density` setting is
|
// Z-axis compression. Filament use at the same `sparse_infill_density` setting is
|
||||||
|
|||||||
@@ -172,6 +172,8 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
|
|||||||
|
|
||||||
// Orca: Infill patterns laid out by an octree, which each connected body always gets of its own.
|
// Orca: Infill patterns laid out by an octree, which each connected body always gets of its own.
|
||||||
inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; }
|
inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; }
|
||||||
|
// Orca: Infill patterns graded by the "tpms_adaptive" option.
|
||||||
|
inline bool is_tpms_adaptive_pattern(InfillPattern pattern) { return pattern == ipGyroid || pattern == ipTpmsD || pattern == ipTpmsFK; }
|
||||||
|
|
||||||
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
|
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
|
||||||
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
|
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
|
||||||
@@ -257,6 +259,26 @@ enum class SurfaceFillOrder {
|
|||||||
Count,
|
Count,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Orca: what the adaptive TPMS density follows: the 3D shape of the object, or its 2D sections normal to an axis.
|
||||||
|
enum class TpmsAdaptiveMode {
|
||||||
|
Disabled,
|
||||||
|
DistanceWarp,
|
||||||
|
SmoothBlend,
|
||||||
|
SteppedShells,
|
||||||
|
Lobes,
|
||||||
|
NormalZ,
|
||||||
|
NormalY,
|
||||||
|
NormalX,
|
||||||
|
Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Orca: how the adaptive TPMS density changes from the object surface to its deepest point.
|
||||||
|
enum class TpmsAdaptiveGradient {
|
||||||
|
Linear,
|
||||||
|
Quadratic,
|
||||||
|
Exponential,
|
||||||
|
};
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
enum class PrintSequence {
|
enum class PrintSequence {
|
||||||
ByLayer,
|
ByLayer,
|
||||||
@@ -774,6 +796,8 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
|
|||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TpmsAdaptiveMode)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TpmsAdaptiveGradient)
|
||||||
|
|
||||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||||
|
|
||||||
@@ -1450,6 +1474,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
// Orca:
|
// Orca:
|
||||||
((ConfigOptionFloatOrPercent, infill_combination_max_layer_height))
|
((ConfigOptionFloatOrPercent, infill_combination_max_layer_height))
|
||||||
((ConfigOptionInt, fill_multiline))
|
((ConfigOptionInt, fill_multiline))
|
||||||
|
((ConfigOptionEnum<TpmsAdaptiveMode>, tpms_adaptive))
|
||||||
|
((ConfigOptionPercent, tpms_interior_density))
|
||||||
|
((ConfigOptionEnum<TpmsAdaptiveGradient>, tpms_adaptive_gradient))
|
||||||
((ConfigOptionBool, gyroid_optimized))
|
((ConfigOptionBool, gyroid_optimized))
|
||||||
// Ironing options
|
// Ironing options
|
||||||
((ConfigOptionEnum<IroningType>, ironing_type))
|
((ConfigOptionEnum<IroningType>, ironing_type))
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
|
#include "ConnectedBodies.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
#include "Fill/FillAdaptive.hpp"
|
#include "Fill/FillAdaptive.hpp"
|
||||||
#include "Fill/Fill.hpp"
|
#include "Fill/Fill.hpp"
|
||||||
#include "Fill/FillLightning.hpp"
|
#include "Fill/FillLightning.hpp"
|
||||||
|
#include "Fill/FillTpmsAdaptive.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
#include "AABBTreeIndirect.hpp"
|
#include "AABBTreeIndirect.hpp"
|
||||||
#include "AABBTreeLines.hpp"
|
#include "AABBTreeLines.hpp"
|
||||||
@@ -746,69 +748,19 @@ void PrintObject::prepare_infill()
|
|||||||
for (Layer *layer : m_layers)
|
for (Layer *layer : m_layers)
|
||||||
layer->lslices_separated_component_ids.clear();
|
layer->lslices_separated_component_ids.clear();
|
||||||
if (needs_separated_components) {
|
if (needs_separated_components) {
|
||||||
const size_t nl = m_layers.size();
|
std::vector<const ExPolygons *> islands;
|
||||||
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
|
islands.reserve(m_layers.size());
|
||||||
for (size_t i = 0; i < nl; ++ i)
|
for (const Layer *layer : m_layers)
|
||||||
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
|
islands.emplace_back(&layer->lslices);
|
||||||
const size_t nreg = offset[nl];
|
size_t bodies = 0;
|
||||||
// Orca: Union-find over every (layer, island).
|
std::vector<std::vector<size_t>> ids = connected_bodies(islands, bodies, [this]() { m_print->throw_if_canceled(); });
|
||||||
std::vector<size_t> parent(nreg);
|
// Orca: Merge the bounding boxes of the islands of each body.
|
||||||
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
|
m_separated_body_bboxes.assign(bodies, BoundingBox());
|
||||||
auto find = [&parent](size_t x) {
|
for (size_t i = 0; i < m_layers.size(); ++ i) {
|
||||||
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
|
|
||||||
return x;
|
|
||||||
};
|
|
||||||
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
|
|
||||||
// Orca: Index the smaller of two consecutive layers instead of scanning every
|
|
||||||
// pair of islands. The tree prunes distant boxes on fragmented models; exact
|
|
||||||
// polygon intersections still decide connectivity for the remaining candidates.
|
|
||||||
for (size_t i = 0; i + 1 < nl; ++ i) {
|
|
||||||
m_print->throw_if_canceled();
|
|
||||||
size_t layer_a = i, layer_b = i + 1;
|
|
||||||
if (m_layers[layer_a]->lslices.size() < m_layers[layer_b]->lslices.size())
|
|
||||||
std::swap(layer_a, layer_b);
|
|
||||||
const Layer *la = m_layers[layer_a], *lb = m_layers[layer_b];
|
|
||||||
if (lb->lslices.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
|
|
||||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
|
||||||
bboxes.reserve(lb->lslices.size());
|
|
||||||
for (size_t b = 0; b < lb->lslices.size(); ++ b)
|
|
||||||
bboxes.emplace_back(b, lb->lslices_bboxes[b]);
|
|
||||||
IslandTree tree;
|
|
||||||
tree.build_modify_input(bboxes);
|
|
||||||
for (size_t a = 0; a < la->lslices.size(); ++ a) {
|
|
||||||
const IslandTree::BoundingBox query(la->lslices_bboxes[a].min, la->lslices_bboxes[a].max);
|
|
||||||
AABBTreeIndirect::traverse(tree,
|
|
||||||
[&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
|
|
||||||
[&](const IslandTree::Node &node) {
|
|
||||||
const size_t b = node.idx;
|
|
||||||
// Orca: Tree boxes include an epsilon, so retain the original box
|
|
||||||
// filter. Already-connected islands cannot change the partition
|
|
||||||
// and need no further polygon intersection.
|
|
||||||
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
|
|
||||||
find(offset[layer_a] + a) != find(offset[layer_b] + b) &&
|
|
||||||
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
|
|
||||||
unite(offset[layer_a] + a, offset[layer_b] + b);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Orca: Number the bodies by their first island and merge the bounding boxes of their islands.
|
|
||||||
std::vector<size_t> body_of_root(nreg, size_t(-1));
|
|
||||||
for (size_t i = 0; i < nl; ++ i) {
|
|
||||||
Layer *layer = m_layers[i];
|
Layer *layer = m_layers[i];
|
||||||
layer->lslices_separated_component_ids.resize(layer->lslices.size());
|
for (size_t a = 0; a < layer->lslices.size(); ++ a)
|
||||||
for (size_t a = 0; a < layer->lslices.size(); ++ a) {
|
m_separated_body_bboxes[ids[i][a]].merge(layer->lslices_bboxes[a]);
|
||||||
size_t &body = body_of_root[find(offset[i] + a)];
|
layer->lslices_separated_component_ids = std::move(ids[i]);
|
||||||
if (body == size_t(-1)) {
|
|
||||||
body = m_separated_body_bboxes.size();
|
|
||||||
m_separated_body_bboxes.emplace_back();
|
|
||||||
}
|
|
||||||
m_separated_body_bboxes[body].merge(layer->lslices_bboxes[a]);
|
|
||||||
layer->lslices_separated_component_ids[a] = body;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1297,6 +1249,38 @@ FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
|
|||||||
return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this), [this]() -> void { this->throw_if_canceled(); }) : FillLightning::GeneratorPtr();
|
return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this), [this]() -> void { this->throw_if_canceled(); }) : FillLightning::GeneratorPtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TpmsRadialFields PrintObject::prepare_tpms_radial_fields() const
|
||||||
|
{
|
||||||
|
TpmsRadialFields fields;
|
||||||
|
std::array<bool, size_t(TpmsAdaptiveMode::Count)> modes{};
|
||||||
|
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id)
|
||||||
|
if (const PrintRegionConfig &config = this->printing_region(region_id).config();
|
||||||
|
config.sparse_infill_density > 0 && config.sparse_infill_density < 100 && is_tpms_adaptive_pattern(config.sparse_infill_pattern))
|
||||||
|
modes[size_t(config.tpms_adaptive.value)] = true;
|
||||||
|
modes[size_t(TpmsAdaptiveMode::Disabled)] = false;
|
||||||
|
if (std::find(modes.begin(), modes.end(), true) == modes.end() || m_layers.empty())
|
||||||
|
return fields;
|
||||||
|
|
||||||
|
std::vector<TpmsRadialField::Slice> slices;
|
||||||
|
slices.reserve(m_layers.size());
|
||||||
|
BoundingBox bbox;
|
||||||
|
for (const Layer *layer : m_layers) {
|
||||||
|
slices.push_back({layer->bottom_z(), layer->print_z, &layer->lslices});
|
||||||
|
bbox.merge(get_extents(layer->lslices));
|
||||||
|
}
|
||||||
|
if (!bbox.defined)
|
||||||
|
return fields;
|
||||||
|
for (size_t mode = 0; mode < modes.size(); ++mode) {
|
||||||
|
if (!modes[mode])
|
||||||
|
continue;
|
||||||
|
// Without a field, the infill falls back to the regular pattern.
|
||||||
|
auto field = std::make_unique<TpmsRadialField>(slices, bbox, TpmsAdaptiveMode(mode), [this]() { m_print->throw_if_canceled(); });
|
||||||
|
if (!field->empty())
|
||||||
|
fields[mode] = std::move(field);
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
void PrintObject::clear_layers()
|
void PrintObject::clear_layers()
|
||||||
{
|
{
|
||||||
if (!m_shared_object) {
|
if (!m_shared_object) {
|
||||||
@@ -1700,6 +1684,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
|| opt_key == "infill_overhang_angle") {
|
|| opt_key == "infill_overhang_angle") {
|
||||||
steps.emplace_back(posInfill);
|
steps.emplace_back(posInfill);
|
||||||
} else if (opt_key == "sparse_infill_pattern"
|
} else if (opt_key == "sparse_infill_pattern"
|
||||||
|
|| opt_key == "tpms_adaptive"
|
||||||
|
|| opt_key == "tpms_interior_density"
|
||||||
|
|| opt_key == "tpms_adaptive_gradient"
|
||||||
// Orca: Body centering now also determines bridge anchors during preparation.
|
// Orca: Body centering now also determines bridge anchors during preparation.
|
||||||
// Invalidating preparation also invalidates infill, including top/bottom surfaces.
|
// Invalidating preparation also invalidates infill, including top/bottom surfaces.
|
||||||
|| opt_key == "center_of_surface_pattern"
|
|| opt_key == "center_of_surface_pattern"
|
||||||
@@ -3207,6 +3194,7 @@ void PrintObject::bridge_over_infill()
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer);
|
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer);
|
||||||
|
this->m_tpms_radial_fields = this->prepare_tpms_radial_fields();
|
||||||
|
|
||||||
std::vector<size_t> layers_to_generate_infill;
|
std::vector<size_t> layers_to_generate_infill;
|
||||||
for (const auto &pair : surfaces_by_layer) {
|
for (const auto &pair : surfaces_by_layer) {
|
||||||
|
|||||||
@@ -1495,6 +1495,40 @@ float its_volume(const indexed_triangle_set &its)
|
|||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MassProperties MassProperties::transformed(const Transform3d &trafo) const
|
||||||
|
{
|
||||||
|
const Matrix3d linear = trafo.linear();
|
||||||
|
const double scale = std::abs(linear.determinant());
|
||||||
|
return { mass * scale, volume * scale, trafo * center, linear * spread * linear.transpose() };
|
||||||
|
}
|
||||||
|
|
||||||
|
MassProperties its_mass_properties(const indexed_triangle_set &its)
|
||||||
|
{
|
||||||
|
if (its.indices.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Signed tetrahedra fanned from a mesh vertex, not the origin, to keep the sums precise far from it.
|
||||||
|
const Vec3d p0 = its.vertices.front().cast<double>();
|
||||||
|
double volume6 = 0.;
|
||||||
|
Vec3d moment24 = Vec3d::Zero();
|
||||||
|
Matrix3d second120 = Matrix3d::Zero();
|
||||||
|
for (const stl_triangle_vertex_indices &face : its.indices) {
|
||||||
|
const Vec3d a = its.vertices[face(0)].cast<double>() - p0;
|
||||||
|
const Vec3d b = its.vertices[face(1)].cast<double>() - p0;
|
||||||
|
const Vec3d c = its.vertices[face(2)].cast<double>() - p0;
|
||||||
|
const Vec3d s = a + b + c;
|
||||||
|
const double v = a.dot(b.cross(c));
|
||||||
|
volume6 += v;
|
||||||
|
moment24 += v * s;
|
||||||
|
second120 += v * (a * a.transpose() + b * b.transpose() + c * c.transpose() + s * s.transpose());
|
||||||
|
}
|
||||||
|
if (volume6 == 0.)
|
||||||
|
return {};
|
||||||
|
const Vec3d center = moment24 / (4. * volume6);
|
||||||
|
const double volume = std::abs(volume6) / 6.;
|
||||||
|
return { volume, volume, p0 + center, second120 / (20. * volume6) - center * center.transpose() };
|
||||||
|
}
|
||||||
|
|
||||||
float its_average_edge_length(const indexed_triangle_set &its)
|
float its_average_edge_length(const indexed_triangle_set &its)
|
||||||
{
|
{
|
||||||
if (its.indices.empty())
|
if (its.indices.empty())
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cereal/specialize.hpp>
|
#include <cereal/specialize.hpp>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
#include "Line.hpp"
|
#include "Line.hpp"
|
||||||
@@ -324,6 +325,20 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
|
|||||||
}
|
}
|
||||||
|
|
||||||
float its_volume(const indexed_triangle_set &its);
|
float its_volume(const indexed_triangle_set &its);
|
||||||
|
// Mass, volume and center of mass of a solid, and the mean over its mass of (x - center)(x - center)^T, from which its
|
||||||
|
// moments of inertia about axes through the center follow.
|
||||||
|
struct MassProperties
|
||||||
|
{
|
||||||
|
double mass{ 0. };
|
||||||
|
double volume{ 0. };
|
||||||
|
Vec3d center{ Vec3d::Zero() };
|
||||||
|
Matrix3d spread{ Matrix3d::Zero() };
|
||||||
|
|
||||||
|
// Under an affine map, which scales mass and volume by its determinant.
|
||||||
|
MassProperties transformed(const Transform3d &trafo) const;
|
||||||
|
};
|
||||||
|
// The solid a closed mesh bounds at unit density, whichever way its faces turn; nothing for a zero volume.
|
||||||
|
MassProperties its_mass_properties(const indexed_triangle_set &its);
|
||||||
float its_average_edge_length(const indexed_triangle_set &its);
|
float its_average_edge_length(const indexed_triangle_set &its);
|
||||||
|
|
||||||
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
|
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
|
||||||
|
|||||||
@@ -149,6 +149,56 @@ std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std:
|
|||||||
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
|
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A plugin page capability exposed as a speed-dial action. source_key = plugin_key
|
||||||
|
// (identity), so a plugin display-name change does not re-key the action.
|
||||||
|
struct PluginPageAction : AppAction
|
||||||
|
{
|
||||||
|
static constexpr const char* kIdPrefix = "plugin_page_action";
|
||||||
|
|
||||||
|
std::string plugin_key;
|
||||||
|
std::string capability;
|
||||||
|
|
||||||
|
// The id an action for (plugin_key, capability) would have - lets refresh_page_capability
|
||||||
|
// remove a gone capability without materialising the action.
|
||||||
|
static std::string id_for(const std::string& plugin_key, const std::string& capability)
|
||||||
|
{ return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key); }
|
||||||
|
|
||||||
|
PluginPageAction(std::string plugin_key_in, std::string capability_in, std::string source_name)
|
||||||
|
: AppAction(kIdPrefix,
|
||||||
|
capability_in.empty() ? plugin_key_in : capability_in, // title
|
||||||
|
plugin_key_in, // source_key
|
||||||
|
std::move(source_name))
|
||||||
|
, plugin_key(std::move(plugin_key_in))
|
||||||
|
, capability(std::move(capability_in))
|
||||||
|
{
|
||||||
|
// Stay classified as a plugin: grouped under "Plugins" and gated by the same run-confirm.
|
||||||
|
this->kind = AppActionKind::Plugin;
|
||||||
|
// Icon is left empty on purpose: the webview builds resources/images/<icon>.svg, which a
|
||||||
|
// plugin filesystem icon path would not resolve to.
|
||||||
|
}
|
||||||
|
|
||||||
|
AppActionRunResult run(const std::string& /*param*/) const override
|
||||||
|
{
|
||||||
|
MainFrame* mf = wxGetApp().mainframe;
|
||||||
|
if (mf)
|
||||||
|
mf->plugin_pages().select_page({PluginCapabilityType::Pages, capability, plugin_key});
|
||||||
|
return {AppActionRunResult::Level::Success};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Builds an action for a page capability, or nullptr if it is not a currently-loaded,
|
||||||
|
// enabled page capability.
|
||||||
|
std::unique_ptr<AppAction> make_page_action(const std::string& plugin_key, const std::string& capability, const std::string& source_name)
|
||||||
|
{
|
||||||
|
PluginManager& manager = PluginManager::instance();
|
||||||
|
if (!manager.is_plugin_loaded(plugin_key))
|
||||||
|
return nullptr;
|
||||||
|
// only_enabled defaults true, so a disabled capability resolves to nullptr here.
|
||||||
|
if (!manager.get_plugin_capability({PluginCapabilityType::Pages, capability, plugin_key}))
|
||||||
|
return nullptr;
|
||||||
|
return std::make_unique<PluginPageAction>(plugin_key, capability, source_name);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- built-in command actions (the speed dial "commands" section) ------
|
// ---- built-in command actions (the speed dial "commands" section) ------
|
||||||
|
|
||||||
constexpr const char* kSettingPrefix = "orca_setting";
|
constexpr const char* kSettingPrefix = "orca_setting";
|
||||||
@@ -311,13 +361,20 @@ void ActionRegistry::init()
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) {
|
auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) {
|
||||||
if (capability.type != PluginCapabilityType::Script || !wxTheApp || wxGetApp().is_closing())
|
if (capability.type != PluginCapabilityType::Script && capability.type != PluginCapabilityType::Pages)
|
||||||
return;
|
return;
|
||||||
const std::string plugin_key = capability.plugin_key;
|
if (!wxTheApp || wxGetApp().is_closing())
|
||||||
const std::string name = capability.name;
|
return;
|
||||||
wxGetApp().CallAfter([this, plugin_key, name, change] {
|
const PluginCapabilityType type = capability.type;
|
||||||
if (!wxGetApp().is_closing())
|
const std::string plugin_key = capability.plugin_key;
|
||||||
|
const std::string name = capability.name;
|
||||||
|
wxGetApp().CallAfter([this, type, plugin_key, name, change] {
|
||||||
|
if (wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
|
if (type == PluginCapabilityType::Script)
|
||||||
this->refresh_capability(plugin_key, name, change);
|
this->refresh_capability(plugin_key, name, change);
|
||||||
|
else
|
||||||
|
this->refresh_page_capability(plugin_key, name, change);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -347,6 +404,16 @@ void ActionRegistry::init()
|
|||||||
upsert(std::move(action));
|
upsert(std::move(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& capability : manager.get_plugin_capabilities("", PluginCapabilityType::Pages)) {
|
||||||
|
if (!capability)
|
||||||
|
continue;
|
||||||
|
const std::string& key = capability->audit_plugin_key();
|
||||||
|
auto it = source_names.find(key);
|
||||||
|
const std::string& source_name = it == source_names.end() ? key : it->second;
|
||||||
|
if (auto action = make_page_action(key, capability->name(), source_name))
|
||||||
|
upsert(std::move(action));
|
||||||
|
}
|
||||||
|
|
||||||
// Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer).
|
// Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer).
|
||||||
// Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct
|
// Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct
|
||||||
// by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in
|
// by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in
|
||||||
@@ -401,6 +468,12 @@ void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange
|
|||||||
if (auto action = make_action(plugin_key, capability->name(), source_name))
|
if (auto action = make_action(plugin_key, capability->name(), source_name))
|
||||||
upsert(std::move(action));
|
upsert(std::move(action));
|
||||||
}
|
}
|
||||||
|
for (const auto& capability : manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Pages)) {
|
||||||
|
if (!capability)
|
||||||
|
continue;
|
||||||
|
if (auto action = make_page_action(plugin_key, capability->name(), source_name))
|
||||||
|
upsert(std::move(action));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
|
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
|
||||||
@@ -420,6 +493,23 @@ void ActionRegistry::refresh_capability(const std::string& plugin_key, const std
|
|||||||
remove(id);
|
remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActionRegistry::refresh_page_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
|
||||||
|
{
|
||||||
|
assert(wxThread::IsMain());
|
||||||
|
|
||||||
|
const std::string id = PluginPageAction::id_for(plugin_key, capability);
|
||||||
|
if (change == ActionChange::Removed) {
|
||||||
|
remove(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginManager& manager = PluginManager::instance();
|
||||||
|
if (auto action = make_page_action(plugin_key, capability, find_loaded_source_name(manager, plugin_key)))
|
||||||
|
upsert(std::move(action));
|
||||||
|
else
|
||||||
|
remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
|
void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
|
||||||
{
|
{
|
||||||
assert(wxThread::IsMain());
|
assert(wxThread::IsMain());
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ private:
|
|||||||
// one plugin's whole action set; refresh_capability touches a single capability.
|
// one plugin's whole action set; refresh_capability touches a single capability.
|
||||||
void refresh_source(const std::string& plugin_key, ActionChange change);
|
void refresh_source(const std::string& plugin_key, ActionChange change);
|
||||||
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
|
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
|
||||||
|
void refresh_page_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
|
||||||
|
|
||||||
bool m_started = false; // init() runs exactly once; guards double-subscription
|
bool m_started = false; // init() runs exactly once; guards double-subscription
|
||||||
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
|
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
|
||||||
|
|||||||
@@ -283,11 +283,12 @@ void BackgroundSlicingProcess::process_fff()
|
|||||||
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
|
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
|
||||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result,
|
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result,
|
||||||
[this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
|
[this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
|
||||||
// Orca: BBL printers post-process the g-code in place here and never re-parse it into a fresh
|
// Orca: BBL printers post-process the g-code in place here, in the file the G-code viewer maps, so
|
||||||
// GCodeProcessorResult, so m_gcode_result->nozzle_group_result (consumed by the H2C print-dispatch
|
// the preview re-reads its moves and line offsets from the edited file. The rest of m_gcode_result,
|
||||||
// nozzle mapping) survives post-processing. No preservation guard is needed on this path.
|
// including nozzle_group_result (consumed by the H2C print-dispatch nozzle mapping), is kept.
|
||||||
if (m_fff_print->is_BBL_printer()) {
|
if (m_fff_print->is_BBL_printer()) {
|
||||||
run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config());
|
if (run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config()))
|
||||||
|
m_fff_print->reload_gcode_moves(m_gcode_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished");
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished");
|
||||||
|
|||||||
@@ -843,9 +843,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
|||||||
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric ||
|
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric ||
|
||||||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral;
|
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral;
|
||||||
|
|
||||||
// gyroid_optimized only applies when the sparse infill pattern is gyroid;
|
// The sparse infill density is the surface density of the adaptive TPMS infill; at 100% the infill is solid.
|
||||||
|
bool have_tpms_infill = have_infill && config->option<ConfigOptionPercent>("sparse_infill_density")->value < 100 &&
|
||||||
|
is_tpms_adaptive_pattern(pattern);
|
||||||
|
toggle_line("tpms_adaptive", have_tpms_infill);
|
||||||
|
bool have_tpms_adaptive = have_tpms_infill && config->opt_enum<TpmsAdaptiveMode>("tpms_adaptive") != TpmsAdaptiveMode::Disabled;
|
||||||
|
toggle_line("tpms_interior_density", have_tpms_adaptive);
|
||||||
|
toggle_line("tpms_adaptive_gradient", have_tpms_adaptive);
|
||||||
|
|
||||||
|
// gyroid_optimized only applies when the sparse infill pattern is gyroid without adaptive density;
|
||||||
// hide the whole line otherwise.
|
// hide the whole line otherwise.
|
||||||
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid);
|
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid && !have_tpms_adaptive);
|
||||||
|
|
||||||
// If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill.
|
// If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill.
|
||||||
if (have_infill) {
|
if (have_infill) {
|
||||||
|
|||||||
@@ -895,10 +895,16 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, f
|
|||||||
auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
|
auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
|
||||||
std::vector<Line> ret;
|
std::vector<Line> ret;
|
||||||
ret.reserve(end_id - start_id + 1);
|
ret.reserve(end_id - start_id + 1);
|
||||||
|
// Orca: m_lines_ends indexes into a memory mapping, so it must be clamped to the mapping. If the
|
||||||
|
// file was modified behind our back (an in-place post-processing script that shrank it), an
|
||||||
|
// unchecked read is an access violation, which the caller's try/catch cannot catch on Windows.
|
||||||
|
const size_t file_size = m_file.size();
|
||||||
for (uint64_t id = start_id; id <= end_id; ++id) {
|
for (uint64_t id = start_id; id <= end_id; ++id) {
|
||||||
// read line from file
|
// read line from file
|
||||||
const size_t start = id == 1 ? 0 : m_lines_ends[id - 2];
|
// Keep one entry per id: render() indexes m_lines by (id - start_id).
|
||||||
const size_t original_len = m_lines_ends[id - 1] - start;
|
const size_t start = id == 1 ? 0 : std::min(m_lines_ends[id - 2], file_size);
|
||||||
|
const size_t end = std::min(m_lines_ends[id - 1], file_size);
|
||||||
|
const size_t original_len = end > start ? end - start : 0;
|
||||||
// A character is four bytes at most, so 55 of them always fit in 220.
|
// A character is four bytes at most, so 55 of them always fit in 220.
|
||||||
const size_t len = std::min(original_len, (size_t) 55 * 4);
|
const size_t len = std::min(original_len, (size_t) 55 * 4);
|
||||||
std::string gline(m_file.data() + start, len);
|
std::string gline(m_file.data() + start, len);
|
||||||
@@ -1435,6 +1441,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
|||||||
wxGetApp().plater()->schedule_background_process();
|
wxGetApp().plater()->schedule_background_process();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_plate_mass = gcode_result.plate_mass;
|
||||||
|
m_object_masses = gcode_result.object_masses;
|
||||||
|
m_body_masses = gcode_result.body_masses;
|
||||||
|
m_support_masses = gcode_result.support_masses;
|
||||||
|
|
||||||
// convert data from PrusaSlicer format to libvgcode format.
|
// convert data from PrusaSlicer format to libvgcode format.
|
||||||
// Belt printers: when the designed (upright) view is active, back-transform
|
// Belt printers: when the designed (upright) view is active, back-transform
|
||||||
@@ -1870,6 +1880,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
|||||||
void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
|
void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
|
||||||
{
|
{
|
||||||
m_loaded_as_preview = true;
|
m_loaded_as_preview = true;
|
||||||
|
m_plate_mass = {};
|
||||||
|
m_object_masses.clear();
|
||||||
|
m_body_masses.clear();
|
||||||
|
m_support_masses.clear();
|
||||||
|
|
||||||
m_move_type_counts.fill(0);
|
m_move_type_counts.fill(0);
|
||||||
for (auto& move_type_times : m_move_type_times)
|
for (auto& move_type_times : m_move_type_times)
|
||||||
@@ -1949,6 +1963,10 @@ void GCodeViewer::reset()
|
|||||||
m_move_type_distances.fill(0.0f);
|
m_move_type_distances.fill(0.0f);
|
||||||
m_print_statistics.reset();
|
m_print_statistics.reset();
|
||||||
m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
|
m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
|
||||||
|
m_plate_mass = {};
|
||||||
|
m_object_masses.clear();
|
||||||
|
m_body_masses.clear();
|
||||||
|
m_support_masses.clear();
|
||||||
m_left_extruder_filament.clear();
|
m_left_extruder_filament.clear();
|
||||||
m_right_extruder_filament.clear();
|
m_right_extruder_filament.clear();
|
||||||
m_sequential_view.gcode_window.reset();
|
m_sequential_view.gcode_window.reset();
|
||||||
|
|||||||
@@ -260,6 +260,10 @@ private:
|
|||||||
GCodeProcessorResult::SettingsIds m_settings_ids;
|
GCodeProcessorResult::SettingsIds m_settings_ids;
|
||||||
|
|
||||||
std::vector<CustomGCode::Item> m_custom_gcode_per_print_z;
|
std::vector<CustomGCode::Item> m_custom_gcode_per_print_z;
|
||||||
|
GCodeProcessorResult::ObjectMass m_plate_mass;
|
||||||
|
std::vector<GCodeProcessorResult::ObjectMass> m_object_masses;
|
||||||
|
std::vector<GCodeProcessorResult::ObjectMass> m_body_masses;
|
||||||
|
std::vector<GCodeProcessorResult::ObjectMass> m_support_masses;
|
||||||
|
|
||||||
bool m_contained_in_bed{ true };
|
bool m_contained_in_bed{ true };
|
||||||
mutable bool m_no_render_path { false };
|
mutable bool m_no_render_path { false };
|
||||||
@@ -343,6 +347,10 @@ public:
|
|||||||
std::vector<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
|
std::vector<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
|
||||||
|
|
||||||
const std::array<size_t,2> &get_layers_z_range() const { return m_viewer.get_layers_view_range(); }
|
const std::array<size_t,2> &get_layers_z_range() const { return m_viewer.get_layers_view_range(); }
|
||||||
|
const GCodeProcessorResult::ObjectMass& get_plate_mass() const { return m_plate_mass; }
|
||||||
|
const std::vector<GCodeProcessorResult::ObjectMass>& get_object_masses() const { return m_object_masses; }
|
||||||
|
const std::vector<GCodeProcessorResult::ObjectMass>& get_body_masses() const { return m_body_masses; }
|
||||||
|
const std::vector<GCodeProcessorResult::ObjectMass>& get_support_masses() const { return m_support_masses; }
|
||||||
size_t get_vertices_count() const { return m_viewer.get_vertices_count(); }
|
size_t get_vertices_count() const { return m_viewer.get_vertices_count(); }
|
||||||
size_t get_layers_count() const { return m_viewer.get_layers_count(); }
|
size_t get_layers_count() const { return m_viewer.get_layers_count(); }
|
||||||
// ORCA: realistic view. Changes whenever the toolpaths casting shadows do.
|
// ORCA: realistic view. Changes whenever the toolpaths casting shadows do.
|
||||||
|
|||||||
@@ -85,7 +85,9 @@
|
|||||||
#include "3DScene.hpp"
|
#include "3DScene.hpp"
|
||||||
#include "BackgroundSlicingProcess.hpp"
|
#include "BackgroundSlicingProcess.hpp"
|
||||||
#include "CameraUtils.hpp"
|
#include "CameraUtils.hpp"
|
||||||
|
#include "GLModel.hpp"
|
||||||
#include "GLShader.hpp"
|
#include "GLShader.hpp"
|
||||||
|
#include "libslic3r/ConnectedBodies.hpp"
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "Tab.hpp"
|
#include "Tab.hpp"
|
||||||
#include "GUI_Preview.hpp"
|
#include "GUI_Preview.hpp"
|
||||||
@@ -136,6 +138,7 @@
|
|||||||
#include <tbb/spin_mutex.h>
|
#include <tbb/spin_mutex.h>
|
||||||
|
|
||||||
#include <boost/functional/hash.hpp>
|
#include <boost/functional/hash.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
@@ -1000,6 +1003,325 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The sums a solid adds to a marker.
|
||||||
|
static GCodeProcessorResult::ObjectMass::Sum mass_sum(const MassProperties& solid)
|
||||||
|
{
|
||||||
|
return { solid.mass, solid.volume, solid.mass * solid.center,
|
||||||
|
solid.mass * (solid.spread.diagonal() + solid.center.cwiseProduct(solid.center)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// On screen, of the plates, the objects, the supports and the bodies, each smaller than the one before, so that markers at
|
||||||
|
// one place still show.
|
||||||
|
static constexpr std::array<double, 4> marker_radii{ 9., 7., 6., 5. };
|
||||||
|
|
||||||
|
// As the canvas toolbar scales for the display's DPI.
|
||||||
|
static double marker_scale(const GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
double scale = canvas.get_scale();
|
||||||
|
#ifdef WIN32
|
||||||
|
scale *= double(get_dpi_for_window(wxGetApp().GetTopWindow())) / double(DPI_DEFAULT);
|
||||||
|
#endif // WIN32
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLCanvas3D::CenterOfMass::Markers GLCanvas3D::CenterOfMass::model_markers(const GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
Markers markers;
|
||||||
|
if (canvas.get_model() == nullptr)
|
||||||
|
return markers;
|
||||||
|
|
||||||
|
struct Instance
|
||||||
|
{
|
||||||
|
Transform3d trafo;
|
||||||
|
std::vector<const GLVolume*> volumes;
|
||||||
|
};
|
||||||
|
std::map<int, std::map<int, Instance>> objects;
|
||||||
|
const ModelObjectPtrs& model_objects = canvas.get_model()->objects;
|
||||||
|
for (const GLVolume* volume : canvas.get_volumes().volumes) {
|
||||||
|
const int obj_idx = volume->object_idx();
|
||||||
|
const int vol_idx = volume->volume_idx();
|
||||||
|
if (!volume->is_active || volume->is_wipe_tower || obj_idx < 0 || obj_idx >= int(model_objects.size()) || vol_idx < 0 ||
|
||||||
|
vol_idx >= int(model_objects[obj_idx]->volumes.size()))
|
||||||
|
continue;
|
||||||
|
Instance& instance = objects[obj_idx][volume->instance_idx()];
|
||||||
|
instance.trafo = volume->get_instance_transformation().get_matrix();
|
||||||
|
instance.volumes.emplace_back(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the filament presets, as the plater config holds the values of the last filament edited only.
|
||||||
|
const PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
|
||||||
|
std::vector<double> filament_densities;
|
||||||
|
for (const std::string& name : preset_bundle.filament_presets)
|
||||||
|
filament_densities.emplace_back(preset_bundle.filaments.find_preset(name, true)->config.opt_float("filament_density", 0));
|
||||||
|
const auto density = [&filament_densities](const ModelVolume& volume) {
|
||||||
|
const size_t filament = size_t(std::max(1, volume.extruder_id()));
|
||||||
|
const double density = filament <= filament_densities.size() ? filament_densities[filament - 1] : 0.;
|
||||||
|
return density > 0. ? density : double(DEFAULT_FILAMENT_DENSITY);
|
||||||
|
};
|
||||||
|
|
||||||
|
// One per plate, of the instances on it.
|
||||||
|
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
||||||
|
std::map<int, Marker> plates;
|
||||||
|
std::map<size_t, MassProperties> meshes;
|
||||||
|
std::map<size_t, Bodies> bodies;
|
||||||
|
for (const auto& [obj_idx, instances] : objects) {
|
||||||
|
const ModelObject& object = *model_objects[obj_idx];
|
||||||
|
// An assembly is sliced, so that its overlapping parts are united and its negative volumes cut away, in the
|
||||||
|
// order of its volumes, as the later one prints where two overlap.
|
||||||
|
std::vector<const GLVolume*> volumes = instances.begin()->second.volumes;
|
||||||
|
std::sort(volumes.begin(), volumes.end(), [](const GLVolume* l, const GLVolume* r) { return l->volume_idx() < r->volume_idx(); });
|
||||||
|
std::vector<MeshInPlace> solids;
|
||||||
|
std::vector<double> densities;
|
||||||
|
std::vector<MeshInPlace> negatives;
|
||||||
|
std::vector<Bodies::Volume> sliced;
|
||||||
|
for (const GLVolume* volume : volumes) {
|
||||||
|
const ModelVolume& model_volume = *object.volumes[volume->volume_idx()];
|
||||||
|
if (!model_volume.is_model_part() && !model_volume.is_negative_volume())
|
||||||
|
continue;
|
||||||
|
const Transform3d trafo = volume->get_volume_transformation().get_matrix();
|
||||||
|
if (model_volume.is_model_part()) {
|
||||||
|
solids.emplace_back(&model_volume.mesh().its, trafo);
|
||||||
|
densities.emplace_back(density(model_volume));
|
||||||
|
} else
|
||||||
|
negatives.emplace_back(&model_volume.mesh().its, trafo);
|
||||||
|
sliced.push_back({ model_volume.id().id, model_volume.is_negative_volume(), model_volume.is_model_part() ? densities.back() : 0., trafo });
|
||||||
|
}
|
||||||
|
const std::vector<SolidBody>* assembly = nullptr;
|
||||||
|
if (solids.size() > 1 || (!solids.empty() && !negatives.empty())) {
|
||||||
|
// Coarser while a part is dragged.
|
||||||
|
const size_t slabs = canvas.is_dragging() ? 100 : 500;
|
||||||
|
const auto cached = m_bodies.find(object.id().id);
|
||||||
|
const bool valid = cached != m_bodies.end() && cached->second.slabs >= slabs && cached->second.volumes == sliced;
|
||||||
|
Bodies& entry = bodies[object.id().id];
|
||||||
|
entry = valid ? std::move(cached->second) : Bodies{ std::move(sliced), slabs, solid_bodies(solids, densities, negatives, slabs) };
|
||||||
|
assembly = &entry.bodies;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [inst_idx, instance] : instances) {
|
||||||
|
// The box of its parts, which the object's size shows.
|
||||||
|
Marker object_marker;
|
||||||
|
object_marker.assembly = assembly != nullptr;
|
||||||
|
for (const GLVolume* volume : instance.volumes)
|
||||||
|
if (object.volumes[volume->volume_idx()]->is_model_part())
|
||||||
|
object_marker.box.merge(volume->transformed_convex_hull_bounding_box());
|
||||||
|
if (assembly != nullptr) {
|
||||||
|
std::vector<Marker> parts;
|
||||||
|
for (const SolidBody& body : *assembly)
|
||||||
|
if (body.mass > 0.) {
|
||||||
|
parts.push_back({ mass_sum(body.transformed(instance.trafo)), body.bounding_box(instance.trafo) });
|
||||||
|
object_marker.sum.add(parts.back().sum);
|
||||||
|
}
|
||||||
|
if (parts.size() > 1)
|
||||||
|
append(markers[mkBody], std::move(parts));
|
||||||
|
} else
|
||||||
|
for (const GLVolume* volume : instance.volumes) {
|
||||||
|
// The parts the object info's volume sums.
|
||||||
|
const ModelVolume& model_volume = *object.volumes[volume->volume_idx()];
|
||||||
|
if (!model_volume.is_model_part())
|
||||||
|
continue;
|
||||||
|
const auto [it, inserted] = meshes.try_emplace(model_volume.id().id);
|
||||||
|
if (inserted) {
|
||||||
|
const auto cached = m_meshes.find(it->first);
|
||||||
|
it->second = cached != m_meshes.end() ? cached->second : its_mass_properties(model_volume.mesh().its);
|
||||||
|
}
|
||||||
|
MassProperties part = it->second.transformed(volume->world_matrix());
|
||||||
|
part.mass *= density(model_volume);
|
||||||
|
object_marker.sum.add(mass_sum(part));
|
||||||
|
}
|
||||||
|
if (object_marker.sum.mass > 0.) {
|
||||||
|
if (const int plate = plate_list.find_instance(obj_idx, inst_idx); plate >= 0) {
|
||||||
|
plates[plate].sum.add(object_marker.sum);
|
||||||
|
plates[plate].box.merge(object_marker.box);
|
||||||
|
}
|
||||||
|
markers[mkObject].emplace_back(std::move(object_marker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_meshes = std::move(meshes);
|
||||||
|
m_bodies = std::move(bodies);
|
||||||
|
for (auto& [plate, marker] : plates)
|
||||||
|
markers[mkPlate].emplace_back(std::move(marker));
|
||||||
|
return markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCanvas3D::CenterOfMass::render(GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
m_drawn = {};
|
||||||
|
const bool preview = canvas.m_canvas_type == ECanvasType::CanvasPreview;
|
||||||
|
// The other gizmos work on the surface the marker would cover.
|
||||||
|
const GLGizmosManager::EType gizmo = canvas.get_gizmos_manager().get_current_type();
|
||||||
|
if (!wxGetApp().show_center_of_mass() || canvas.m_design_canvas ||
|
||||||
|
!(canvas.m_canvas_type == ECanvasType::CanvasView3D || (preview && canvas.m_render_preview)) ||
|
||||||
|
(gizmo != GLGizmosManager::Undefined && gizmo != GLGizmosManager::Move && gizmo != GLGizmosManager::Rotate &&
|
||||||
|
gizmo != GLGizmosManager::Scale && gizmo != GLGizmosManager::Flatten))
|
||||||
|
return;
|
||||||
|
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||||
|
if (shader == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Preview adds markers for what is printed up to the top layer shown.
|
||||||
|
if (preview) {
|
||||||
|
const GCodeViewer& gcode_viewer = canvas.get_gcode_viewer();
|
||||||
|
m_top_layer = gcode_viewer.get_layers_z_range()[1];
|
||||||
|
const auto add = [this](const GCodeProcessorResult::ObjectMass& mass, MarkerKind kind) {
|
||||||
|
if (const Sum total = mass.total(); total.mass > 0.)
|
||||||
|
m_drawn[0][kind].push_back({ total, mass.box, mass.assembly });
|
||||||
|
if (!mass.printed_up_to_layer.empty())
|
||||||
|
if (const Sum& sum = mass.printed_up_to_layer[std::min(m_top_layer, mass.printed_up_to_layer.size() - 1)]; sum.mass > 0.)
|
||||||
|
m_drawn[1][kind].push_back({ sum, mass.box, mass.assembly });
|
||||||
|
};
|
||||||
|
add(gcode_viewer.get_plate_mass(), mkPlate);
|
||||||
|
for (const GCodeProcessorResult::ObjectMass& object : gcode_viewer.get_object_masses())
|
||||||
|
add(object, mkObject);
|
||||||
|
for (const GCodeProcessorResult::ObjectMass& body : gcode_viewer.get_body_masses())
|
||||||
|
add(body, mkBody);
|
||||||
|
for (const GCodeProcessorResult::ObjectMass& support : gcode_viewer.get_support_masses())
|
||||||
|
add(support, mkSupport);
|
||||||
|
} else
|
||||||
|
m_drawn[0] = model_markers(canvas);
|
||||||
|
if (std::all_of(m_drawn.begin(), m_drawn.end(),
|
||||||
|
[](const Markers& markers) { return std::all_of(markers.begin(), markers.end(), [](const auto& kind) { return kind.empty(); }); }))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_octants[0].is_initialized()) {
|
||||||
|
// A resolution divisible by 4 puts every triangle within one octant.
|
||||||
|
const GLModel::Geometry sphere = smooth_sphere(32, 1.f);
|
||||||
|
std::array<GLModel::Geometry, 2> octants;
|
||||||
|
for (size_t i = 0; i + 2 < sphere.indices_count(); i += 3) {
|
||||||
|
const std::array<unsigned int, 3> ids = { sphere.extract_index(i), sphere.extract_index(i + 1), sphere.extract_index(i + 2) };
|
||||||
|
const Vec3f c = sphere.extract_position_3(ids[0]) + sphere.extract_position_3(ids[1]) + sphere.extract_position_3(ids[2]);
|
||||||
|
GLModel::Geometry& octant = octants[c.x() * c.y() * c.z() > 0.f ? 0 : 1];
|
||||||
|
for (const unsigned int id : ids)
|
||||||
|
octant.add_vertex(sphere.extract_position_3(id), sphere.extract_normal_3(id));
|
||||||
|
const auto n = (unsigned int)octant.vertices_count();
|
||||||
|
octant.add_triangle(n - 3, n - 2, n - 1);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < octants.size(); ++i)
|
||||||
|
m_octants[i].init_from(std::move(octants[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||||
|
const double scale = marker_scale(canvas) * camera.get_inv_zoom();
|
||||||
|
|
||||||
|
// Seen through the object it lies in; culling keeps the sphere's far half behind its near one.
|
||||||
|
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||||
|
glsafe(::glEnable(GL_CULL_FACE));
|
||||||
|
shader->start_using();
|
||||||
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||||
|
shader->set_uniform("view_normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3));
|
||||||
|
shader->set_uniform("emission_factor", 0.1f);
|
||||||
|
const std::array<std::array<ColorRGBA, 2>, mkCount> colors = { {
|
||||||
|
{ ColorRGBA(0.1f, 0.1f, 0.1f, 1.f), ColorRGBA::WHITE() },
|
||||||
|
{ ColorRGBA(0x5A / 255.f, 0x9B / 255.f, 0xD4 / 255.f, 1.f), ColorRGBA::WHITE() },
|
||||||
|
{ ColorRGBA(0.f, 0.6f, 0.f, 1.f), ColorRGBA(0.1f, 0.1f, 0.1f, 1.f) },
|
||||||
|
{ ColorRGBA(0.7f, 0.f, 0.f, 1.f), ColorRGBA::YELLOW() },
|
||||||
|
} };
|
||||||
|
const auto draw = [&](const Markers& markers, float alpha) {
|
||||||
|
for (size_t kind = 0; kind < mkCount; ++kind)
|
||||||
|
for (const Marker& marker : markers[kind]) {
|
||||||
|
shader->set_uniform("view_model_matrix", view_matrix * Geometry::translation_transform(marker.center()) *
|
||||||
|
Geometry::scale_transform(marker_radii[kind] * scale));
|
||||||
|
for (size_t i = 0; i < m_octants.size(); ++i) {
|
||||||
|
ColorRGBA color = colors[kind][i];
|
||||||
|
color.a(alpha);
|
||||||
|
m_octants[i].set_color(color);
|
||||||
|
m_octants[i].render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Preview fades the finished parts' markers under those of what is printed so far.
|
||||||
|
draw(m_drawn[0], preview ? 0.4f : 1.f);
|
||||||
|
draw(m_drawn[1], 1.f);
|
||||||
|
shader->stop_using();
|
||||||
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLCanvas3D::CenterOfMass::on_left_down(GLCanvas3D& canvas, const Vec2d& mouse)
|
||||||
|
{
|
||||||
|
const bool shown = m_picked.has_value();
|
||||||
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
const double scale = marker_scale(canvas) * camera.get_inv_zoom();
|
||||||
|
m_picked.reset();
|
||||||
|
// In the order they cover each other: what is printed so far over the finished print, smaller kinds over larger ones.
|
||||||
|
for (size_t set = m_drawn.size(); set-- > 0 && !m_picked;)
|
||||||
|
for (size_t kind = mkCount; kind-- > 0 && !m_picked;)
|
||||||
|
for (size_t index = 0; index < m_drawn[set][kind].size(); ++index) {
|
||||||
|
const Vec3d center = m_drawn[set][kind][index].center();
|
||||||
|
const std::vector<Vec3d> ends = { center, center + marker_radii[kind] * scale * camera.get_dir_right() };
|
||||||
|
const Points screen = CameraUtils::project(camera, ends);
|
||||||
|
if ((screen[0].cast<double>() - mouse).norm() <= (screen[1] - screen[0]).cast<double>().norm()) {
|
||||||
|
m_picked = Pick{ set, kind, index, m_drawn[set][kind].size() };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shown || m_picked)
|
||||||
|
canvas._set_overlay_as_dirty();
|
||||||
|
return m_picked.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCanvas3D::CenterOfMass::render_details(GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
if (!m_picked)
|
||||||
|
return;
|
||||||
|
const Pick& pick = *m_picked;
|
||||||
|
const std::vector<Marker>& markers = m_drawn[pick.set][pick.kind];
|
||||||
|
// Gone with the markers, or with what it stood for.
|
||||||
|
if (markers.size() != pick.count) {
|
||||||
|
m_picked.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Marker& marker = markers[pick.index];
|
||||||
|
const Sum& sum = marker.sum;
|
||||||
|
const Vec3d center = marker.center();
|
||||||
|
// About the axes through the center, from how far the mass spreads along the two others.
|
||||||
|
const Vec3d spread = (sum.second / sum.mass - center.cwiseProduct(center)).cwiseMax(0.);
|
||||||
|
const Vec3d inertia = sum.mass * Vec3d(spread.y() + spread.z(), spread.x() + spread.z(), spread.x() + spread.y());
|
||||||
|
|
||||||
|
// Beside the marker.
|
||||||
|
const Point screen = CameraUtils::project(wxGetApp().plater()->get_camera(), center);
|
||||||
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||||
|
imgui.set_next_window_pos(float(screen.x() + 2. * marker_radii[pick.kind] * marker_scale(canvas)), float(screen.y()), ImGuiCond_Always, 0.f, 0.5f);
|
||||||
|
const std::string title = pick.kind == mkPlate ? _u8L("Plate center of mass") :
|
||||||
|
pick.kind == mkBody ? _u8L("Part center of mass") :
|
||||||
|
pick.kind == mkSupport ? _u8L("Support center of mass") :
|
||||||
|
marker.assembly ? _u8L("Assembly center of mass") :
|
||||||
|
_u8L("Object center of mass");
|
||||||
|
bool open = true;
|
||||||
|
imgui.begin(title + "###center_of_mass", &open,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
|
||||||
|
if (ImGui::IsWindowAppearing())
|
||||||
|
imgui.set_requires_extra_frame();
|
||||||
|
if (canvas.get_canvas_type() == ECanvasType::CanvasPreview)
|
||||||
|
imgui.text(pick.set == 0 ? _u8L("Finished print") : (boost::format(_u8L("Printed up to layer %1%")) % (m_top_layer + 1)).str());
|
||||||
|
// Masses are in mg, volumes in mm³.
|
||||||
|
const auto xyz = [](const Vec3d& v, const char* format, const std::string& unit) {
|
||||||
|
return (boost::format(format) % v.x() % v.y() % v.z()).str() + " " + unit;
|
||||||
|
};
|
||||||
|
if (ImGui::BeginTable("##center_of_mass_details", 2)) {
|
||||||
|
const auto row = [](const std::string& label, const std::string& value) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, label);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGuiWrapper::text(value);
|
||||||
|
};
|
||||||
|
row(_u8L("Weight"), (boost::format("%.2f g") % (sum.mass / 1000.)).str());
|
||||||
|
row(_u8L("Volume"), (boost::format(u8"%.2f cm³") % (sum.volume / 1000.)).str());
|
||||||
|
if (marker.box.defined) {
|
||||||
|
row(_u8L("Center in bounding box"), xyz(center - marker.box.min, "X: %.2f, Y: %.2f, Z: %.2f", _u8L("mm")));
|
||||||
|
row(_u8L("Bounding box size"), xyz(marker.box.size(), "X: %.2f, Y: %.2f, Z: %.2f", _u8L("mm")));
|
||||||
|
}
|
||||||
|
row(_u8L("Moment of inertia"), xyz(inertia / 1000., "X: %.0f, Y: %.0f, Z: %.0f", u8"g·mm²"));
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
imgui.end();
|
||||||
|
if (!open) {
|
||||||
|
m_picked.reset();
|
||||||
|
canvas._set_overlay_as_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GLCanvas3D::Tooltip::set_text(const std::string& text)
|
void GLCanvas3D::Tooltip::set_text(const std::string& text)
|
||||||
{
|
{
|
||||||
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
|
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
|
||||||
@@ -2520,6 +2842,9 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
|
|||||||
m_frame_profiler.mark("ssao");
|
m_frame_profiler.mark("ssao");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After the occlusion pass, which would shade it as the surface behind it.
|
||||||
|
m_center_of_mass.render(*this);
|
||||||
|
|
||||||
if (_is_fxaa_enabled()) {
|
if (_is_fxaa_enabled()) {
|
||||||
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
||||||
m_frame_profiler.mark("fxaa");
|
m_frame_profiler.mark("fxaa");
|
||||||
@@ -4513,6 +4838,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A click on a center of mass marker shows its details instead of selecting.
|
||||||
|
if (evt.LeftDown() && !mouse_in_layer_editing && m_center_of_mass.on_left_down(*this, pos.cast<double>())) {
|
||||||
|
m_mouse.ignore_left_up = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool any_gizmo_active = m_gizmos.get_current() != nullptr;
|
bool any_gizmo_active = m_gizmos.get_current() != nullptr;
|
||||||
|
|
||||||
std::map<MouseButton, MouseAction> button_mappings;
|
std::map<MouseButton, MouseAction> button_mappings;
|
||||||
@@ -9363,6 +9694,7 @@ void GLCanvas3D::_render_overlays()
|
|||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
m_labels.render(sorted_instances);
|
m_labels.render(sorted_instances);
|
||||||
|
m_center_of_mass.render_details(*this);
|
||||||
|
|
||||||
_render_3d_navigator();
|
_render_3d_navigator();
|
||||||
|
|
||||||
@@ -10350,6 +10682,12 @@ void GLCanvas3D::_render_canvas_toolbar()
|
|||||||
[p]{p->show_view3D_labels(!p->are_view3D_labels_shown());}
|
[p]{p->show_view3D_labels(!p->are_view3D_labels_shown());}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create_menu_item( _utf8(L("Center of mass")),
|
||||||
|
m_canvas_type != ECanvasType::CanvasAssembleView && !m_design_canvas, // work on prepare and preview
|
||||||
|
wxGetApp().show_center_of_mass(),
|
||||||
|
[this]{wxGetApp().toggle_show_center_of_mass(); m_dirty = true;}
|
||||||
|
);
|
||||||
|
|
||||||
// Belt printers, G-code preview only: show the raw machine-frame G-code instead of
|
// Belt printers, G-code preview only: show the raw machine-frame G-code instead of
|
||||||
// the designed (upright) view. This menu is the only place the toggle lives (plus
|
// the designed (upright) view. This menu is the only place the toggle lives (plus
|
||||||
// its shortcut); the reload is deferred (CallAfter) so the preview is not rebuilt
|
// its shortcut); the reload is deferred (CallAfter) so the preview is not rebuilt
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#define slic3r_GLCanvas3D_hpp_
|
#define slic3r_GLCanvas3D_hpp_
|
||||||
|
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Point.hpp"
|
||||||
|
#include "libslic3r/ConnectedBodies.hpp"
|
||||||
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include "slic3r/GUI/3DScene.hpp"
|
#include "slic3r/GUI/3DScene.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -30,6 +33,7 @@
|
|||||||
#include "Gizmos/GLGizmosManager.hpp"
|
#include "Gizmos/GLGizmosManager.hpp"
|
||||||
#include "GUI_ObjectLayers.hpp"
|
#include "GUI_ObjectLayers.hpp"
|
||||||
#include "GLSelectionRectangle.hpp"
|
#include "GLSelectionRectangle.hpp"
|
||||||
|
#include "GLModel.hpp"
|
||||||
#include "MeshUtils.hpp"
|
#include "MeshUtils.hpp"
|
||||||
#include "GCodeViewer.hpp"
|
#include "GCodeViewer.hpp"
|
||||||
#include "Camera.hpp"
|
#include "Camera.hpp"
|
||||||
@@ -477,6 +481,69 @@ class GLCanvas3D
|
|||||||
void render(const std::vector<const ModelInstance*>& sorted_instances) const;
|
void render(const std::vector<const ModelInstance*>& sorted_instances) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CenterOfMass
|
||||||
|
{
|
||||||
|
using Sum = GCodeProcessorResult::ObjectMass::Sum;
|
||||||
|
enum MarkerKind : size_t { mkPlate, mkObject, mkSupport, mkBody, mkCount };
|
||||||
|
// A marker's mass and the box of what it stands for.
|
||||||
|
struct Marker
|
||||||
|
{
|
||||||
|
Sum sum;
|
||||||
|
BoundingBoxf3 box;
|
||||||
|
// Of an object, whether it is an assembly.
|
||||||
|
bool assembly{ false };
|
||||||
|
|
||||||
|
Vec3d center() const { return sum.moment / sum.mass; }
|
||||||
|
};
|
||||||
|
// The plates', each object instance's, its supports' and each body of an assembly's.
|
||||||
|
using Markers = std::array<std::vector<Marker>, mkCount>;
|
||||||
|
|
||||||
|
// The marker's two colors of alternating octants.
|
||||||
|
std::array<GLModel, 2> m_octants;
|
||||||
|
// Mass properties at unit density of each ModelVolume's mesh, by ModelVolume id, which a new mesh changes.
|
||||||
|
std::map<size_t, MassProperties> m_meshes;
|
||||||
|
// The connected bodies of each assembly in its own coordinates, by ModelObject id, with the volumes they were sliced from.
|
||||||
|
struct Bodies
|
||||||
|
{
|
||||||
|
struct Volume
|
||||||
|
{
|
||||||
|
size_t id;
|
||||||
|
bool negative;
|
||||||
|
double density;
|
||||||
|
Transform3d trafo;
|
||||||
|
|
||||||
|
bool operator==(const Volume& other) const
|
||||||
|
{
|
||||||
|
return id == other.id && negative == other.negative && density == other.density && trafo.matrix() == other.trafo.matrix();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<Volume> volumes;
|
||||||
|
size_t slabs{ 0 };
|
||||||
|
std::vector<SolidBody> bodies;
|
||||||
|
};
|
||||||
|
std::map<size_t, Bodies> m_bodies;
|
||||||
|
// The markers drawn last: of the finished print and, in Preview, of what is printed up to the top layer shown.
|
||||||
|
std::array<Markers, 2> m_drawn;
|
||||||
|
size_t m_top_layer{ 0 };
|
||||||
|
// The marker whose details are shown, with the number of its kind then.
|
||||||
|
struct Pick
|
||||||
|
{
|
||||||
|
size_t set;
|
||||||
|
size_t kind;
|
||||||
|
size_t index;
|
||||||
|
size_t count;
|
||||||
|
};
|
||||||
|
std::optional<Pick> m_picked;
|
||||||
|
|
||||||
|
Markers model_markers(const GLCanvas3D& canvas);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void render(GLCanvas3D& canvas);
|
||||||
|
// Shows the details of the marker under the mouse, else hides them; whether it hit one.
|
||||||
|
bool on_left_down(GLCanvas3D& canvas, const Vec2d& mouse);
|
||||||
|
void render_details(GLCanvas3D& canvas);
|
||||||
|
};
|
||||||
|
|
||||||
class Tooltip
|
class Tooltip
|
||||||
{
|
{
|
||||||
std::string m_text;
|
std::string m_text;
|
||||||
@@ -733,6 +800,7 @@ private:
|
|||||||
int m_selected_extruder;
|
int m_selected_extruder;
|
||||||
|
|
||||||
Labels m_labels;
|
Labels m_labels;
|
||||||
|
CenterOfMass m_center_of_mass;
|
||||||
Tooltip m_tooltip;
|
Tooltip m_tooltip;
|
||||||
bool m_tooltip_enabled{ true };
|
bool m_tooltip_enabled{ true };
|
||||||
Slope m_slope;
|
Slope m_slope;
|
||||||
|
|||||||
@@ -7204,11 +7204,11 @@ void GUI_App::sync_preset(Preset* preset, bool force)
|
|||||||
|
|
||||||
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name;
|
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name;
|
||||||
if (preset->type == Preset::Type::TYPE_FILAMENT) {
|
if (preset->type == Preset::Type::TYPE_FILAMENT) {
|
||||||
preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
|
preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
|
||||||
} else if (preset->type == Preset::Type::TYPE_PRINT) {
|
} else if (preset->type == Preset::Type::TYPE_PRINT) {
|
||||||
preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
|
preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
|
||||||
} else if (preset->type == Preset::Type::TYPE_PRINTER) {
|
} else if (preset->type == Preset::Type::TYPE_PRINTER) {
|
||||||
preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
|
preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7907,7 +7907,7 @@ void GUI_App::force_push_conflicting_preset(const std::string& setting_id)
|
|||||||
? OrcaCloudServiceAgent::generate_uuid_for_setting_id(preset.name, user_id)
|
? OrcaCloudServiceAgent::generate_uuid_for_setting_id(preset.name, user_id)
|
||||||
: preset.setting_id;
|
: preset.setting_id;
|
||||||
if (preset_id == setting_id) {
|
if (preset_id == setting_id) {
|
||||||
coll->set_sync_info_and_save(preset.name, setting_id, "update", 0);
|
coll->set_sync_info_and_save(preset.name, setting_id, "update", 0, user_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -435,6 +435,9 @@ public:
|
|||||||
bool show_outline() const { return app_config->get_bool("show_outline"); }
|
bool show_outline() const { return app_config->get_bool("show_outline"); }
|
||||||
void toggle_show_outline() const { app_config->set_bool("show_outline", !show_outline()); }
|
void toggle_show_outline() const { app_config->set_bool("show_outline", !show_outline()); }
|
||||||
|
|
||||||
|
bool show_center_of_mass() const { return app_config->get_bool("show_center_of_mass"); }
|
||||||
|
void toggle_show_center_of_mass() const { app_config->set_bool("show_center_of_mass", !show_center_of_mass()); }
|
||||||
|
|
||||||
wxString get_inf_dialog_contect () {return m_info_dialog_content;};
|
wxString get_inf_dialog_contect () {return m_info_dialog_content;};
|
||||||
|
|
||||||
std::vector<std::string> split_str(std::string src, std::string separator);
|
std::vector<std::string> split_str(std::string src, std::string separator);
|
||||||
|
|||||||
@@ -148,6 +148,9 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
|||||||
{"sparse_infill_density", "", 1},
|
{"sparse_infill_density", "", 1},
|
||||||
{"fill_multiline", "", 1},
|
{"fill_multiline", "", 1},
|
||||||
{"sparse_infill_pattern", "", 1},
|
{"sparse_infill_pattern", "", 1},
|
||||||
|
{"tpms_adaptive", "", 1},
|
||||||
|
{"tpms_interior_density", "", 1},
|
||||||
|
{"tpms_adaptive_gradient", "", 1},
|
||||||
{"sparse_infill_smooth_factor", "", 1},
|
{"sparse_infill_smooth_factor", "", 1},
|
||||||
{"lateral_lattice_angle_1", "", 1},
|
{"lateral_lattice_angle_1", "", 1},
|
||||||
{"lateral_lattice_angle_2", "", 1},
|
{"lateral_lattice_angle_2", "", 1},
|
||||||
|
|||||||
@@ -15076,7 +15076,8 @@ bool Plater::priv::undo_redo_blocked_by_job()
|
|||||||
return false;
|
return false;
|
||||||
notification_manager->push_notification(NotificationType::CustomNotification,
|
notification_manager->push_notification(NotificationType::CustomNotification,
|
||||||
NotificationManager::NotificationLevel::RegularNotificationLevel,
|
NotificationManager::NotificationLevel::RegularNotificationLevel,
|
||||||
_u8L("Cannot undo or redo while an operation is running. Stop it first."));
|
_u8L("Cannot undo or redo while an operation is running. Stop the operation, or wait "
|
||||||
|
"for it to finish and then retry."));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21229,6 +21230,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
|||||||
opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") {
|
opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") {
|
||||||
update_scheduled = true;
|
update_scheduled = true;
|
||||||
}
|
}
|
||||||
|
// Orca: the center of mass markers weigh the parts by it.
|
||||||
|
else if (opt_key == "filament_density" && wxGetApp().show_center_of_mass())
|
||||||
|
p->view3D->get_canvas3d()->set_as_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bed_shape_changed)
|
if (bed_shape_changed)
|
||||||
|
|||||||
@@ -2961,6 +2961,9 @@ void TabPrint::build()
|
|||||||
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
|
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
|
||||||
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
||||||
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
||||||
|
optgroup->append_single_option_line("tpms_adaptive", "strength_settings_patterns#adaptive-density");
|
||||||
|
optgroup->append_single_option_line("tpms_interior_density", "strength_settings_patterns#interior-density");
|
||||||
|
optgroup->append_single_option_line("tpms_adaptive_gradient", "strength_settings_patterns#adaptive-gradient");
|
||||||
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
||||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
|
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
|
||||||
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
#include <wx/gdicmn.h>
|
#include <wx/gdicmn.h>
|
||||||
#include <wx/webview.h>
|
#include <wx/webview.h>
|
||||||
|
|
||||||
|
#ifdef __WXGTK__
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
WebDialog::WebDialog(wxWindow* parent,
|
WebDialog::WebDialog(wxWindow* parent,
|
||||||
@@ -52,6 +56,17 @@ WebDialog::WebDialog(wxWindow* parent,
|
|||||||
Bind(wxEVT_CLOSE_WINDOW, &WebDialog::on_close_window, this);
|
Bind(wxEVT_CLOSE_WINDOW, &WebDialog::on_close_window, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebDialog::use_normal_window_type()
|
||||||
|
{
|
||||||
|
#ifdef __WXGTK__
|
||||||
|
// wxGTK types every wxDialog as a dialog, and Mutter offers no maximize for anything but a
|
||||||
|
// normal window. Mutter also stops hiding a normal window from the taskbar, so keep it hidden
|
||||||
|
// as a dialog with a parent is everywhere else.
|
||||||
|
gtk_window_set_type_hint(GTK_WINDOW(m_widget), GDK_WINDOW_TYPE_HINT_NORMAL);
|
||||||
|
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(m_widget), TRUE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void WebDialog::add_user_scripts()
|
void WebDialog::add_user_scripts()
|
||||||
{
|
{
|
||||||
if (wxWebView* wv = browser()) {
|
if (wxWebView* wv = browser()) {
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ public:
|
|||||||
|
|
||||||
bool is_open() const { return m_open; }
|
bool is_open() const { return m_open; }
|
||||||
|
|
||||||
|
// Lets a modeless window be maximized on X11; call before Show(). Modal ones stay dialogs so the
|
||||||
|
// window manager keeps routing focus from the parent to them.
|
||||||
|
void use_normal_window_type();
|
||||||
|
|
||||||
// The payload submitted via window.orca.submit() (modal use), if any.
|
// The payload submitted via window.orca.submit() (modal use), if any.
|
||||||
const std::optional<nlohmann::json>& result() const { return m_result; }
|
const std::optional<nlohmann::json>& result() const { return m_result; }
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user