mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 09:11:08 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3071d525ee |
@@ -707,9 +707,12 @@ 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' }}
|
||||||
run: |
|
uses: rickstaa/action-create-tag@v1
|
||||||
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
|
with:
|
||||||
git push -f origin refs/tags/nightly-builds
|
tag: "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 libgl1 libegl1 libwebkit2gtk-4.1-0
|
libopengl0 libglu1-mesa 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 libgl1 libegl1 libwebkit2gtk-4.1-0
|
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
|
||||||
|
|
||||||
- name: Run the parity harness
|
- name: Run the parity harness
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
# 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,8 +379,10 @@ 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)
|
||||||
@@ -476,6 +478,7 @@ 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,4 +1,6 @@
|
|||||||
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
@@ -0,0 +1,14 @@
|
|||||||
|
# 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
@@ -0,0 +1,44 @@
|
|||||||
|
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
@@ -0,0 +1,73 @@
|
|||||||
|
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
@@ -0,0 +1,251 @@
|
|||||||
|
# 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
@@ -0,0 +1 @@
|
|||||||
|
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
@@ -0,0 +1,101 @@
|
|||||||
|
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
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
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 ()
|
||||||
@@ -191,7 +191,7 @@ public:
|
|||||||
tail->next = std::move(p);
|
tail->next = std::move(p);
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
}
|
}
|
||||||
disrupt_wait_for_data();
|
data_cond.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
void wait_and_pop(T& value)
|
void wait_and_pop(T& value)
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
# 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 with `connected_bodies()` before bridges are detected,
|
into 3D connected bodies before bridges are detected, so bridge anchors and
|
||||||
so bridge anchors and printed infill share one origin. Islands on adjacent layers belong to one body
|
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
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ uniform vec3 palette_lab[64];
|
|||||||
uniform vec3 palette_rgb[64];
|
uniform vec3 palette_rgb[64];
|
||||||
uniform int palette_count;
|
uniform int palette_count;
|
||||||
uniform bool pure_only; // match against single filaments only (flat-colour image)
|
uniform bool pure_only; // match against single filaments only (flat-colour image)
|
||||||
// The entry's two filaments, equal for a single filament - only so a mix can be told apart. An entry's
|
// How each entry prints. Every entry names a single filament: a mix is given its own mixed filament
|
||||||
// palette_rgb is already the colour it prints in (for a mix, its mixed filament slot's).
|
// slot, whose components the slicer alternates per print layer, so the fragment just looks that slot's
|
||||||
|
// colour up.
|
||||||
uniform int palette_a[64];
|
uniform int palette_a[64];
|
||||||
uniform int palette_b[64];
|
uniform int palette_b[64];
|
||||||
uniform float prefer_pure_de; // PREFER_PURE_DE: how much better than a single filament a mix must be
|
uniform vec3 filament_rgb[16];
|
||||||
|
uniform int filament_count;
|
||||||
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
|
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
|
||||||
uniform bool has_color_tex;
|
uniform bool has_color_tex;
|
||||||
uniform bool volume_mirrored;
|
uniform bool volume_mirrored;
|
||||||
@@ -208,16 +210,31 @@ int nearest_palette_entry(vec3 rgb)
|
|||||||
best = i;
|
best = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The same bias make_palette_quantizer() applies: a mix is an interleave, so it is only worth taking
|
// The same bias make_palette_quantizer() applies (PREFER_PURE_DE = 10): a mix is an interleave, so
|
||||||
// when it beats the nearest single filament by a visible step - otherwise the preview shows mixes
|
// it is only worth taking when it beats the nearest single filament by a visible step. Without it
|
||||||
// where the bake prints a single filament. Compared on the distances rather than their squares, so
|
// this picked a mix for almost every fragment - with four filaments the palette is 4 pure entries
|
||||||
// the margin means the same thing as it does on the CPU (up to CIE76 against CIEDE2000, the
|
// against 30 mixes - while the bake picked a single filament for most of them, so the preview
|
||||||
// approximation already noted above).
|
// interleaved the whole wall where the bake interleaves only patches. Compared on the distances
|
||||||
if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < prefer_pure_de)
|
// rather than their squares, so the threshold means the same thing as it does on the CPU (up to
|
||||||
|
// CIE76 against CIEDE2000, the approximation already noted above).
|
||||||
|
if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < 10.0)
|
||||||
best = best_pure;
|
best = best_pure;
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One 2x2 Bayer cell, {0, 2; 3, 1}, for x and y in {0, 1}.
|
||||||
|
|
||||||
|
// The colour the printer lays down at world point `pos` for palette entry `index`. Every entry names a
|
||||||
|
// single filament: a mix is given its own mixed filament slot, whose components the slicer alternates
|
||||||
|
// per print layer, so there is nothing left to interleave here.
|
||||||
|
vec3 printed_color(int index)
|
||||||
|
{
|
||||||
|
int a = palette_a[index];
|
||||||
|
if (a < 0 || a >= filament_count)
|
||||||
|
return palette_rgb[index]; // no filament to resolve to: the entry's own colour
|
||||||
|
return filament_rgb[a];
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||||
@@ -337,11 +354,16 @@ void main()
|
|||||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||||
|
|
||||||
// Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour.
|
// Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour -
|
||||||
|
// and, where that is a mix, the filament the interleave puts here, so the pattern that prints shows.
|
||||||
// Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as
|
// Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as
|
||||||
// the same material under the same light, and the relief this preview exists to show is unaffected.
|
// the same material under the same light, and the relief this preview exists to show is unaffected.
|
||||||
vec3 albedo = uniform_color.rgb;
|
vec3 albedo = uniform_color.rgb;
|
||||||
if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0)
|
if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0)
|
||||||
albedo = palette_rgb[nearest_palette_entry(texture2D(color_tex, color_uv).rgb)];
|
// tex_pos, not world_pos: the bake resolves the interleave in the bake frame (world
|
||||||
|
// orientation and scale about the volume's origin, see texture_displacement_bake_frame()), so
|
||||||
|
// measuring z from the bed instead shifted the band phase by the volume origin's height - a
|
||||||
|
// different filament in the same place than the bake produces.
|
||||||
|
albedo = printed_color(nearest_palette_entry(texture2D(color_tex, color_uv).rgb));
|
||||||
gl_FragColor = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
|
gl_FragColor = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,13 @@ uniform vec3 palette_lab[64];
|
|||||||
uniform vec3 palette_rgb[64];
|
uniform vec3 palette_rgb[64];
|
||||||
uniform int palette_count;
|
uniform int palette_count;
|
||||||
uniform bool pure_only; // match against single filaments only (flat-colour image)
|
uniform bool pure_only; // match against single filaments only (flat-colour image)
|
||||||
// The entry's two filaments, equal for a single filament - only so a mix can be told apart. An entry's
|
// How each entry prints. Every entry names a single filament: a mix is given its own mixed filament
|
||||||
// palette_rgb is already the colour it prints in (for a mix, its mixed filament slot's).
|
// slot, whose components the slicer alternates per print layer, so the fragment just looks that slot's
|
||||||
|
// colour up.
|
||||||
uniform int palette_a[64];
|
uniform int palette_a[64];
|
||||||
uniform int palette_b[64];
|
uniform int palette_b[64];
|
||||||
uniform float prefer_pure_de; // PREFER_PURE_DE: how much better than a single filament a mix must be
|
uniform vec3 filament_rgb[16];
|
||||||
|
uniform int filament_count;
|
||||||
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
|
uniform sampler2D color_tex; // the layer's colour image, sampled at the same uv as the height
|
||||||
uniform bool has_color_tex;
|
uniform bool has_color_tex;
|
||||||
uniform bool volume_mirrored;
|
uniform bool volume_mirrored;
|
||||||
@@ -274,16 +276,31 @@ int nearest_palette_entry(vec3 rgb)
|
|||||||
best = i;
|
best = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The same bias make_palette_quantizer() applies: a mix is an interleave, so it is only worth taking
|
// The same bias make_palette_quantizer() applies (PREFER_PURE_DE = 10): a mix is an interleave, so
|
||||||
// when it beats the nearest single filament by a visible step - otherwise the preview shows mixes
|
// it is only worth taking when it beats the nearest single filament by a visible step. Without it
|
||||||
// where the bake prints a single filament. Compared on the distances rather than their squares, so
|
// this picked a mix for almost every fragment - with four filaments the palette is 4 pure entries
|
||||||
// the margin means the same thing as it does on the CPU (up to CIE76 against CIEDE2000, the
|
// against 30 mixes - while the bake picked a single filament for most of them, so the preview
|
||||||
// approximation already noted above).
|
// interleaved the whole wall where the bake interleaves only patches. Compared on the distances
|
||||||
if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < prefer_pure_de)
|
// rather than their squares, so the threshold means the same thing as it does on the CPU (up to
|
||||||
|
// CIE76 against CIEDE2000, the approximation already noted above).
|
||||||
|
if (best_pure >= 0 && palette_a[best] != palette_b[best] && sqrt(bd_pure) - sqrt(bd) < 10.0)
|
||||||
best = best_pure;
|
best = best_pure;
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One 2x2 Bayer cell, {0, 2; 3, 1}, for x and y in {0, 1}.
|
||||||
|
|
||||||
|
// The colour the printer lays down at world point `pos` for palette entry `index`. Every entry names a
|
||||||
|
// single filament: a mix is given its own mixed filament slot, whose components the slicer alternates
|
||||||
|
// per print layer, so there is nothing left to interleave here.
|
||||||
|
vec3 printed_color(int index)
|
||||||
|
{
|
||||||
|
int a = palette_a[index];
|
||||||
|
if (a < 0 || a >= filament_count)
|
||||||
|
return palette_rgb[index]; // no filament to resolve to: the entry's own colour
|
||||||
|
return filament_rgb[a];
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||||
@@ -425,11 +442,16 @@ void main()
|
|||||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||||
|
|
||||||
// Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour.
|
// Diffuse albedo: the image's colour at this fragment, snapped to the nearest printable colour -
|
||||||
|
// and, where that is a mix, the filament the interleave puts here, so the pattern that prints shows.
|
||||||
// Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as
|
// Only the albedo - the specular term (intensity.y) stays white - so a coloured fragment reads as
|
||||||
// the same material under the same light, and the relief this preview exists to show is unaffected.
|
// the same material under the same light, and the relief this preview exists to show is unaffected.
|
||||||
vec3 albedo = uniform_color.rgb;
|
vec3 albedo = uniform_color.rgb;
|
||||||
if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0)
|
if (palette_count > 0 && has_color_tex && have_uv && weight > 0.0)
|
||||||
albedo = palette_rgb[nearest_palette_entry(texture(color_tex, color_uv).rgb)];
|
// tex_pos, not world_pos: the bake resolves the interleave in the bake frame (world
|
||||||
|
// orientation and scale about the volume's origin, see texture_displacement_bake_frame()), so
|
||||||
|
// measuring z from the bed instead shifted the band phase by the volume origin's height - a
|
||||||
|
// different filament in the same place than the bake produces.
|
||||||
|
albedo = printed_color(nearest_palette_entry(texture(color_tex, color_uv).rgb));
|
||||||
out_color = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
|
out_color = vec4(vec3(intensity.y) + albedo * intensity.x, uniform_color.a);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
#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)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
@@ -0,0 +1,495 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,488 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,734 @@
|
|||||||
|
#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() {}
|
||||||
@@ -23,11 +23,15 @@ 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,6 +31,8 @@ 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,6 +55,21 @@ 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:
|
||||||
@@ -202,6 +217,12 @@ 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,6 +11,7 @@ export REQUIRED_DEV_PACKAGES=(
|
|||||||
file
|
file
|
||||||
gettext
|
gettext
|
||||||
git
|
git
|
||||||
|
glew
|
||||||
gst-plugins-good
|
gst-plugins-good
|
||||||
gstreamer
|
gstreamer
|
||||||
gtk3
|
gtk3
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export REQUIRED_DEV_PACKAGES=(
|
|||||||
file
|
file
|
||||||
gettext
|
gettext
|
||||||
git
|
git
|
||||||
|
glew
|
||||||
gst-plugins-good
|
gst-plugins-good
|
||||||
gstreamer
|
gstreamer
|
||||||
gtk3
|
gtk3
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ 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
|
||||||
libgl-dev
|
libglew-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-libGL-devel
|
mesa-libGLU-devel
|
||||||
ninja-build
|
ninja-build
|
||||||
openssl-devel
|
openssl-devel
|
||||||
perl-FindBin
|
perl-FindBin
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ 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
|
||||||
@@ -30,7 +31,6 @@ 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
|
||||||
Mesa-libGL-devel
|
glu-devel
|
||||||
ninja-build
|
ninja-build
|
||||||
openssl-devel
|
openssl-devel
|
||||||
perl-FindBin-Real
|
perl-FindBin-Real
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ 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" >&2
|
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0 and libglu1-mesa" >&2
|
||||||
echo "On Arch/CachyOS, install: libglvnd" >&2
|
echo "On Arch/CachyOS, install: libglvnd" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -411,9 +411,6 @@ 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,8 +112,6 @@ 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
|
||||||
|
|||||||
@@ -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 or provided to a ray-tracer to
|
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to
|
||||||
// deal with various parts of it according to the supported CSG types...
|
// 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
|
||||||
|
|||||||
@@ -1,292 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "ColorDecomposeRecipe.hpp"
|
#include "ColorDecomposeRecipe.hpp"
|
||||||
#include "Config.hpp"
|
|
||||||
#include "FilamentMixerModel.hpp"
|
#include "FilamentMixerModel.hpp"
|
||||||
#include "LocalesUtils.hpp"
|
#include "LocalesUtils.hpp"
|
||||||
|
|
||||||
@@ -428,59 +427,6 @@ std::vector<double> parse_mixed_ratios(const std::string &str, size_t n_componen
|
|||||||
return ratios;
|
return ratios;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string format_mixed_components(const std::vector<unsigned int> &components)
|
|
||||||
{
|
|
||||||
std::string out;
|
|
||||||
for (size_t i = 0; i < components.size(); ++i) {
|
|
||||||
if (i > 0)
|
|
||||||
out += ",";
|
|
||||||
out += std::to_string(components[i]);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format_mixed_ratios(const std::vector<int> &weights)
|
|
||||||
{
|
|
||||||
int sum = std::accumulate(weights.begin(), weights.end(), 0);
|
|
||||||
if (sum <= 0)
|
|
||||||
sum = 100;
|
|
||||||
CNumericLocalesSetter c_locale_setter;
|
|
||||||
std::string out;
|
|
||||||
for (size_t i = 0; i < weights.size(); ++i) {
|
|
||||||
if (i > 0)
|
|
||||||
out += ",";
|
|
||||||
char buf[32];
|
|
||||||
std::snprintf(buf, sizeof(buf), "%.4f", float(weights[i]) / float(sum));
|
|
||||||
out += buf;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
int find_fixed_mixed_filament(const ConfigBase &project_config,
|
|
||||||
const std::vector<unsigned int> &components,
|
|
||||||
const std::vector<int> &weights)
|
|
||||||
{
|
|
||||||
const auto *is_mixed = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
|
||||||
const auto *comps = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
|
||||||
const auto *ratios = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
|
||||||
if (is_mixed == nullptr || comps == nullptr || ratios == nullptr)
|
|
||||||
return -1;
|
|
||||||
// Created lazily with the first mixed slot, so an older project may not have it at all.
|
|
||||||
const auto *gradient = project_config.option<ConfigOptionBools>("filament_mixed_gradient");
|
|
||||||
|
|
||||||
const std::string comp_str = format_mixed_components(components);
|
|
||||||
const std::string ratio_str = format_mixed_ratios(weights);
|
|
||||||
for (size_t i = 0; i < is_mixed->values.size(); ++i) {
|
|
||||||
if (!is_mixed->values[i] || i >= comps->values.size() || i >= ratios->values.size())
|
|
||||||
continue;
|
|
||||||
if (gradient != nullptr && i < gradient->values.size() && gradient->values[i])
|
|
||||||
continue;
|
|
||||||
if (comps->values[i] == comp_str && ratios->values[i] == ratio_str)
|
|
||||||
return int(i);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_any_mixed_filament(const std::vector<unsigned char> &is_mixed)
|
bool has_any_mixed_filament(const std::vector<unsigned char> &is_mixed)
|
||||||
{
|
{
|
||||||
for (unsigned char v : is_mixed)
|
for (unsigned char v : is_mixed)
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ConfigBase;
|
|
||||||
|
|
||||||
// Photoshop-style gradient curve control point in [0,1] x [0,1].
|
// Photoshop-style gradient curve control point in [0,1] x [0,1].
|
||||||
// (x, y) is the anchor position; (m_in, m_out) are optional cubic Hermite tangent
|
// (x, y) is the anchor position; (m_in, m_out) are optional cubic Hermite tangent
|
||||||
// overrides. NaN means "use the PCHIP-computed default", which is the case for plain
|
// overrides. NaN means "use the PCHIP-computed default", which is the case for plain
|
||||||
@@ -96,22 +94,6 @@ std::vector<unsigned int> parse_mixed_components(const std::string &str);
|
|||||||
// Normalizes so the sum equals 1.0.
|
// Normalizes so the sum equals 1.0.
|
||||||
std::vector<double> parse_mixed_ratios(const std::string &str, size_t n_components);
|
std::vector<double> parse_mixed_ratios(const std::string &str, size_t n_components);
|
||||||
|
|
||||||
// The text a mixed slot stores in filament_mixed_components, e.g. {1, 3} → "1,3".
|
|
||||||
std::string format_mixed_components(const std::vector<unsigned int> &components);
|
|
||||||
|
|
||||||
// The text a mixed slot stores in filament_mixed_sublayer_ratios: the weights normalised to sum to 1,
|
|
||||||
// four decimals, e.g. {1, 2} → "0.3333,0.6667". A non-positive sum is read as 100.
|
|
||||||
std::string format_mixed_ratios(const std::vector<int> &weights);
|
|
||||||
|
|
||||||
// The 0-based index of the mixed slot in `project_config` that blends `components` (1-based physical
|
|
||||||
// filaments) in `weights` at a fixed ratio, or -1 when there is none. Matched on the stored text, as
|
|
||||||
// format_mixed_components() and format_mixed_ratios() write it. A gradient slot never matches: its
|
|
||||||
// ratio drifts from layer to layer, so it is not the blend asked for even where its stored ratios
|
|
||||||
// are the same.
|
|
||||||
int find_fixed_mixed_filament(const ConfigBase &project_config,
|
|
||||||
const std::vector<unsigned int> &components,
|
|
||||||
const std::vector<int> &weights);
|
|
||||||
|
|
||||||
// Returns true if any element in is_mixed is true.
|
// Returns true if any element in is_mixed is true.
|
||||||
// ConfigOptionBools stores values as std::vector<unsigned char>.
|
// ConfigOptionBools stores values as std::vector<unsigned char>.
|
||||||
bool has_any_mixed_filament(const std::vector<unsigned char> &is_mixed);
|
bool has_any_mixed_filament(const std::vector<unsigned char> &is_mixed);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
#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"
|
||||||
@@ -2591,156 +2590,6 @@ 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();
|
||||||
@@ -3263,7 +3112,6 @@ 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,6 +89,7 @@ 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();
|
||||||
@@ -2603,10 +2604,6 @@ 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
|
||||||
@@ -3705,7 +3702,6 @@ 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;
|
||||||
|
|
||||||
@@ -3845,7 +3841,6 @@ 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) {
|
||||||
@@ -5474,9 +5469,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -7285,73 +7277,6 @@ 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,7 +3,6 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -36,9 +35,6 @@ 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"
|
||||||
@@ -274,44 +270,9 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
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;
|
||||||
@@ -399,10 +360,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
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;
|
||||||
@@ -1142,15 +1099,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
};
|
};
|
||||||
#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;
|
||||||
@@ -1178,7 +1126,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
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;
|
||||||
@@ -1333,13 +1280,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
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) {
|
||||||
@@ -1594,8 +1534,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
|
|
||||||
//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.
|
||||||
|
|||||||
@@ -5381,15 +5381,13 @@ 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;
|
||||||
// A config loaded over the full defaults has a one-entry index even when the file has none.
|
if (!config.option<ConfigOptionInts>("filament_self_index")) {
|
||||||
ConfigOptionInts* filament_self_index_opt = config.option<ConfigOptionInts>("filament_self_index", true);
|
std::vector<int>& filament_self_indice = config.option<ConfigOptionInts>("filament_self_index", true)->values;
|
||||||
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(filament_self_index_opt->values);
|
std::vector<int> filament_self_indice = std::move(config.option<ConfigOptionInts>("filament_self_index")->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");
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -748,19 +747,69 @@ 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) {
|
||||||
std::vector<const ExPolygons *> islands;
|
const size_t nl = m_layers.size();
|
||||||
islands.reserve(m_layers.size());
|
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
|
||||||
for (const Layer *layer : m_layers)
|
for (size_t i = 0; i < nl; ++ i)
|
||||||
islands.emplace_back(&layer->lslices);
|
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
|
||||||
size_t bodies = 0;
|
const size_t nreg = offset[nl];
|
||||||
std::vector<std::vector<size_t>> ids = connected_bodies(islands, bodies, [this]() { m_print->throw_if_canceled(); });
|
// Orca: Union-find over every (layer, island).
|
||||||
// Orca: Merge the bounding boxes of the islands of each body.
|
std::vector<size_t> parent(nreg);
|
||||||
m_separated_body_bboxes.assign(bodies, BoundingBox());
|
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
|
||||||
for (size_t i = 0; i < m_layers.size(); ++ i) {
|
auto find = [&parent](size_t x) {
|
||||||
|
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];
|
||||||
for (size_t a = 0; a < layer->lslices.size(); ++ a)
|
layer->lslices_separated_component_ids.resize(layer->lslices.size());
|
||||||
m_separated_body_bboxes[ids[i][a]].merge(layer->lslices_bboxes[a]);
|
for (size_t a = 0; a < layer->lslices.size(); ++ a) {
|
||||||
layer->lslices_separated_component_ids = std::move(ids[i]);
|
size_t &body = body_of_root[find(offset[i] + a)];
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,20 +247,6 @@ void smooth_height_pixels(std::vector<uint8_t> &pixels, int width, int height, f
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool height_texture_has_color(const TextureDisplacementLayer &layer)
|
|
||||||
{
|
|
||||||
if (layer.empty())
|
|
||||||
return false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_decoded_texture_cache.mutex);
|
|
||||||
const auto it = g_decoded_texture_cache.entries.find(layer.image_data.get());
|
|
||||||
if (it != g_decoded_texture_cache.entries.end() && it->second.first.lock() == layer.image_data)
|
|
||||||
return it->second.second.has_color();
|
|
||||||
}
|
|
||||||
// Not decoded yet. Decoding caches the raw image, so this happens once per image.
|
|
||||||
return decode_height_texture(layer).has_color();
|
|
||||||
}
|
|
||||||
|
|
||||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer)
|
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer)
|
||||||
{
|
{
|
||||||
DecodedHeightTexture result;
|
DecodedHeightTexture result;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#ifndef slic3r_TextureDisplacement_hpp_
|
#ifndef slic3r_TextureDisplacement_hpp_
|
||||||
#define slic3r_TextureDisplacement_hpp_
|
#define slic3r_TextureDisplacement_hpp_
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -388,10 +387,6 @@ struct TextureDisplacementOptions
|
|||||||
// image (TextureDetail::flat_colors): a texture of flat colours prints in single filaments, a
|
// image (TextureDetail::flat_colors): a texture of flat colours prints in single filaments, a
|
||||||
// photograph or gradient in mixes. Off forces single filaments everywhere.
|
// photograph or gradient in mixes. Off forces single filaments everywhere.
|
||||||
bool color_mix_enabled = true;
|
bool color_mix_enabled = true;
|
||||||
// The most mixes the palette may offer. Every mix a bake paints with becomes a mixed filament slot,
|
|
||||||
// so this is also the most slots one bake can add. The mixes themselves are picked from the
|
|
||||||
// texture's colours, those that improve the match the most coming first.
|
|
||||||
int color_mix_count = 8;
|
|
||||||
// Majority-filter passes over the assigned colours. See TextureColorRequest::despeckle_passes -
|
// Majority-filter passes over the assigned colours. See TextureColorRequest::despeckle_passes -
|
||||||
// this is the control for it, and 2 is enough to clear the salt-and-pepper an image with detail
|
// this is the control for it, and 2 is enough to clear the salt-and-pepper an image with detail
|
||||||
// finer than the mesh leaves behind, without eating features that are genuinely a facet wide.
|
// finer than the mesh leaves behind, without eating features that are genuinely a facet wide.
|
||||||
@@ -401,7 +396,7 @@ struct TextureDisplacementOptions
|
|||||||
{
|
{
|
||||||
ar(displace_border, smooth_enabled, smooth_strength, smooth_iterations, smooth_skip_border,
|
ar(displace_border, smooth_enabled, smooth_strength, smooth_iterations, smooth_skip_border,
|
||||||
pipeline_v2, v2_refine_mm, v2_regularize, v2_max_triangles_k,
|
pipeline_v2, v2_refine_mm, v2_regularize, v2_max_triangles_k,
|
||||||
v2_relocate, color_mix_enabled, color_despeckle, color_mix_count);
|
v2_relocate, color_mix_enabled, color_despeckle);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -485,10 +480,6 @@ struct DecodedHeightTexture
|
|||||||
// DecodedHeightTexture if image_data is empty or is not a PNG at all.
|
// DecodedHeightTexture if image_data is empty or is not a PNG at all.
|
||||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer);
|
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer);
|
||||||
|
|
||||||
// decode_height_texture(layer).has_color(), answered from the decode cache rather than from a copy of the
|
|
||||||
// texture - cheap enough to ask every frame. Smoothing does not change it, so the raw decode is what is read.
|
|
||||||
bool height_texture_has_color(const TextureDisplacementLayer &layer);
|
|
||||||
|
|
||||||
// Maps a linear RGB colour in [0, 1] to an index into the caller's palette, or -1 for "no colour".
|
// Maps a linear RGB colour in [0, 1] to an index into the caller's palette, or -1 for "no colour".
|
||||||
//
|
//
|
||||||
// Deliberately a callback rather than a function here: matching a colour to a filament is a
|
// Deliberately a callback rather than a function here: matching a colour to a filament is a
|
||||||
@@ -503,14 +494,12 @@ using ColorQuantizeFn = std::function<int(const Vec3f &)>;
|
|||||||
// interleaving, which the slicer does per print layer. Plain data, so it can be captured into a job.
|
// interleaving, which the slicer does per print layer. Plain data, so it can be captured into a job.
|
||||||
struct PrintableColor
|
struct PrintableColor
|
||||||
{
|
{
|
||||||
Vec3f rgb = Vec3f::Zero(); // what it looks like; for a mix, the colour its mixed filament slot shows
|
Vec3f rgb = Vec3f::Zero(); // what it looks like; for a mix, the perceptual average of the two
|
||||||
int a = 0; // filament index
|
int a = 0; // filament index
|
||||||
int b = 0; // the second filament; == a for a pure entry
|
int b = 0; // the second filament; == a for a pure entry
|
||||||
int num = 1; // a's share of the interleave, out of `den`
|
int num = 1; // a's share of the interleave, out of `den`
|
||||||
int den = 1;
|
int den = 1;
|
||||||
bool is_mix() const { return a != b; }
|
bool is_mix() const { return a != b; }
|
||||||
// a's share in percent, the form a mixed filament slot is created from.
|
|
||||||
int a_percent() const { return int(std::lround(100.0 * double(num) / double(den))); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Everything needed to colour a mesh, captured on the main thread and handed to a job. An empty
|
// Everything needed to colour a mesh, captured on the main thread and handed to a job. An empty
|
||||||
@@ -773,10 +762,9 @@ struct TextureColorRequest
|
|||||||
float min_color_region_mm2 = 0.5f;
|
float min_color_region_mm2 = 0.5f;
|
||||||
// Filled per *base mesh* triangle (the bake is topology-preserving, so this indexes the returned
|
// Filled per *base mesh* triangle (the bake is topology-preserving, so this indexes the returned
|
||||||
// mesh too): the quantize callback's index plus one, or 0 for "this triangle takes no colour from
|
// mesh too): the quantize callback's index plus one, or 0 for "this triangle takes no colour from
|
||||||
// the texture". The +1 lines up with EnforcerBlockerType, where 0 is NONE ("use the volume's own
|
// the texture". The +1 is not arbitrary - it lines up with EnforcerBlockerType, where 0 is NONE
|
||||||
// filament"): where every palette entry is a filament, these go straight to a TriangleSelector. A
|
// ("use the volume's own filament") and 1..16 are Extruder1..16, so the caller can hand these
|
||||||
// palette with mixes maps each index to the mix's filament slot first (see
|
// straight to a TriangleSelector without a second mapping table.
|
||||||
// GLGizmoTextureDisplacement::palette_filaments()).
|
|
||||||
std::vector<uint8_t> *out_triangle = nullptr;
|
std::vector<uint8_t> *out_triangle = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1495,40 +1495,6 @@ 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,7 +9,6 @@
|
|||||||
#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"
|
||||||
@@ -325,20 +324,6 @@ 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);
|
||||||
|
|||||||
@@ -1441,10 +1441,6 @@ 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
|
||||||
@@ -1880,10 +1876,6 @@ 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)
|
||||||
@@ -1963,10 +1955,6 @@ 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,10 +260,6 @@ 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 };
|
||||||
@@ -347,10 +343,6 @@ 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,9 +85,7 @@
|
|||||||
#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"
|
||||||
@@ -138,7 +136,6 @@
|
|||||||
#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>
|
||||||
|
|
||||||
@@ -1003,325 +1000,6 @@ 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.
|
||||||
@@ -2842,9 +2520,6 @@ 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");
|
||||||
@@ -4838,12 +4513,6 @@ 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;
|
||||||
@@ -9694,7 +9363,6 @@ 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();
|
||||||
|
|
||||||
@@ -10682,12 +10350,6 @@ 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,9 +2,6 @@
|
|||||||
#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>
|
||||||
@@ -33,7 +30,6 @@
|
|||||||
#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"
|
||||||
@@ -481,69 +477,6 @@ 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;
|
||||||
@@ -800,7 +733,6 @@ 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;
|
||||||
|
|||||||
@@ -396,6 +396,8 @@ public:
|
|||||||
Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
|
Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
|
||||||
HMSQuery* get_hms_query() { return hms_query; }
|
HMSQuery* get_hms_query() { return hms_query; }
|
||||||
NetworkAgent* getAgent() { return m_agent; }
|
NetworkAgent* getAgent() { return m_agent; }
|
||||||
|
// Version that wrote the app config before this run; empty when there was no app config.
|
||||||
|
const boost::optional<Semver>& last_config_version() const { return m_last_config_version; }
|
||||||
|
|
||||||
// Reconcile the live printer agent with the stored preset selection.
|
// Reconcile the live printer agent with the stored preset selection.
|
||||||
void switch_printer_agent();
|
void switch_printer_agent();
|
||||||
@@ -435,9 +437,6 @@ 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);
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ Vec3f GLGizmoPainterBase::get_tilt_up_direction() const
|
|||||||
return build_plate_tilt_up_direction().cast<float>();
|
return build_plate_tilt_up_direction().cast<float>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoPainterBase::render_triangles(const Selection& selection, const ModelVolume* skip) const
|
void GLGizmoPainterBase::render_triangles(const Selection& selection) const
|
||||||
{
|
{
|
||||||
auto* shader = wxGetApp().get_shader("mm_gouraud");
|
auto* shader = wxGetApp().get_shader("mm_gouraud");
|
||||||
if (!shader)
|
if (!shader)
|
||||||
@@ -135,8 +135,6 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection, const Mode
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
++mesh_id;
|
++mesh_id;
|
||||||
if (mv == skip)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Transform3d trafo_matrix;
|
Transform3d trafo_matrix;
|
||||||
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
|
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ public:
|
|||||||
virtual ~TriangleSelectorGUI() = default;
|
virtual ~TriangleSelectorGUI() = default;
|
||||||
|
|
||||||
virtual void render(ImGuiWrapper* imgui, const Transform3d& matrix);
|
virtual void render(ImGuiWrapper* imgui, const Transform3d& matrix);
|
||||||
// The seed-fill contour alone, as render() last built it - for a gizmo that draws over the selector
|
|
||||||
// and has to put the contour back on top.
|
|
||||||
void render_paint_contour(const Transform3d& matrix);
|
|
||||||
//void render(const Transform3d& matrix) { this->render(nullptr, matrix); }
|
//void render(const Transform3d& matrix) { this->render(nullptr, matrix); }
|
||||||
void set_wireframe_needed(bool need_wireframe) { m_need_wireframe = need_wireframe; }
|
void set_wireframe_needed(bool need_wireframe) { m_need_wireframe = need_wireframe; }
|
||||||
bool get_wireframe_needed() { return m_need_wireframe; }
|
bool get_wireframe_needed() { return m_need_wireframe; }
|
||||||
@@ -93,6 +90,7 @@ protected:
|
|||||||
GLModel m_paint_contour;
|
GLModel m_paint_contour;
|
||||||
|
|
||||||
void update_paint_contour();
|
void update_paint_contour();
|
||||||
|
void render_paint_contour(const Transform3d& matrix);
|
||||||
|
|
||||||
bool m_need_wireframe {false};
|
bool m_need_wireframe {false};
|
||||||
};
|
};
|
||||||
@@ -233,8 +231,7 @@ public:
|
|||||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Draws every model part's selector, except `skip`'s when given.
|
virtual void render_triangles(const Selection& selection) const;
|
||||||
virtual void render_triangles(const Selection& selection, const ModelVolume* skip = nullptr) const;
|
|
||||||
void render_cursor();
|
void render_cursor();
|
||||||
void render_cursor_circle();
|
void render_cursor_circle();
|
||||||
void render_cursor_sphere(const Transform3d& trafo) const;
|
void render_cursor_sphere(const Transform3d& trafo) const;
|
||||||
@@ -331,9 +328,6 @@ protected:
|
|||||||
|
|
||||||
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
|
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
|
||||||
|
|
||||||
// True while a paint or erase stroke is under way.
|
|
||||||
bool is_painting() const { return m_button_down != Button::None; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
|
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "ColorSpaceConvert.hpp"
|
#include "ColorSpaceConvert.hpp"
|
||||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||||
#include "libslic3r/Color.hpp"
|
#include "libslic3r/Color.hpp"
|
||||||
#include "libslic3r/FilamentMixer.hpp"
|
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/MeshBoolean.hpp"
|
#include "libslic3r/MeshBoolean.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
@@ -70,7 +69,6 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <numeric>
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -259,54 +257,11 @@ TriangleSelector::TriangleSplittingData remap_texture_paint_spatial(
|
|||||||
// entry to fill.
|
// entry to fill.
|
||||||
constexpr int PALETTE_LUT_EDGE = 24;
|
constexpr int PALETTE_LUT_EDGE = 24;
|
||||||
|
|
||||||
// The shaded preview shader's palette arrays. The palette itself stays within the paint mask's
|
// Ceiling on the printable palette, which bounds that fill cost (and the shader's uniform array).
|
||||||
// EnforcerBlockerType::ExtruderMax states, since every entry has to become a filament.
|
|
||||||
constexpr int PALETTE_MAX_ENTRIES = 64;
|
constexpr int PALETTE_MAX_ENTRIES = 64;
|
||||||
|
// Ceiling on the filaments the palette's entries can refer to (the shaded preview shader's filament_rgb[]);
|
||||||
// A mix is an interleave that only reads as its colour from a distance; up close it is stripes. So it
|
// mmu segmentation stops at Extruder16 anyway.
|
||||||
// is spent only where it beats the nearest single filament by this much (CIEDE2000). Two is about
|
constexpr int PALETTE_MAX_FILAMENTS = 16;
|
||||||
// where a side-by-side difference stops being arguable; a margin of ten already turns most of a
|
|
||||||
// greyscale ramp - the shape a height texture actually traces - back into single filaments. The
|
|
||||||
// quantizer, the mix ranking and the shaded preview shader all apply it, so they agree on where a mix
|
|
||||||
// is used.
|
|
||||||
constexpr float PREFER_PURE_DE = 2.f;
|
|
||||||
|
|
||||||
// mix_targets(): the most pixels read per layer, and the histogram bins kept over all layers. Together
|
|
||||||
// they bound rank_mixes() to candidates x MIX_TARGET_BINS colour differences, the same order as filling
|
|
||||||
// the quantizer's lookup cube.
|
|
||||||
constexpr size_t MIX_TARGET_SAMPLES = size_t(1) << 20;
|
|
||||||
constexpr size_t MIX_TARGET_BINS = 256;
|
|
||||||
// rank_mixes() stops once the best remaining mix would improve the match by less than this, in
|
|
||||||
// CIEDE2000 averaged over every pixel of the colouring layers (see mix_targets()): a mix that only
|
|
||||||
// touches a few stray pixels is not worth a filament slot.
|
|
||||||
constexpr float MIN_MIX_GAIN = 0.05f;
|
|
||||||
|
|
||||||
// The project's mixed filament slots, one string each, as the palette cache compares them: anything
|
|
||||||
// that changes which of them a mix can reuse changes this.
|
|
||||||
std::vector<std::string> mixed_slot_signature(const DynamicPrintConfig &project_config)
|
|
||||||
{
|
|
||||||
std::vector<std::string> out;
|
|
||||||
const auto *is_mixed = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
|
||||||
const auto *comps = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
|
||||||
const auto *ratios = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
|
||||||
const auto *gradient = project_config.option<ConfigOptionBools>("filament_mixed_gradient");
|
|
||||||
if (is_mixed == nullptr || comps == nullptr || ratios == nullptr)
|
|
||||||
return out;
|
|
||||||
for (size_t i = 0; i < is_mixed->values.size(); ++i)
|
|
||||||
if (is_mixed->values[i])
|
|
||||||
out.push_back(std::to_string(i) + ':' + (i < comps->values.size() ? comps->values[i] : std::string()) + '|' +
|
|
||||||
(i < ratios->values.size() ? ratios->values[i] : std::string()) + '|' +
|
|
||||||
(gradient != nullptr && i < gradient->values.size() && gradient->values[i] ? "g" : ""));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether two palettes would draw and print the same.
|
|
||||||
bool same_palette(const std::vector<PrintableColor> &l, const std::vector<PrintableColor> &r)
|
|
||||||
{
|
|
||||||
return std::equal(l.begin(), l.end(), r.begin(), r.end(), [](const PrintableColor &x, const PrintableColor &y) {
|
|
||||||
return x.a == y.a && x.b == y.b && x.num == y.num && x.den == y.den && x.rgb == y.rgb;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// sRGB (0..1) <-> CIELAB, D65. Exactly what the preview shader's srgb_to_lab() computes, so the CPU
|
// sRGB (0..1) <-> CIELAB, D65. Exactly what the preview shader's srgb_to_lab() computes, so the CPU
|
||||||
// quantizer, the mixed-palette entries and the per-fragment preview all match in the same space.
|
// quantizer, the mixed-palette entries and the per-fragment preview all match in the same space.
|
||||||
@@ -503,11 +458,6 @@ std::string GLGizmoTextureDisplacement::on_get_name() const
|
|||||||
return _u8L("Texture displacement");
|
return _u8L("Texture displacement");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmoTextureDisplacement::on_is_activable() const
|
|
||||||
{
|
|
||||||
return m_parent.get_canvas_type() != GLCanvas3D::CanvasAssembleView && GLGizmoPainterBase::on_is_activable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::on_shutdown()
|
void GLGizmoTextureDisplacement::on_shutdown()
|
||||||
{
|
{
|
||||||
m_parent.toggle_model_objects_visibility(true);
|
m_parent.toggle_model_objects_visibility(true);
|
||||||
@@ -515,25 +465,10 @@ void GLGizmoTextureDisplacement::on_shutdown()
|
|||||||
m_shaded_preview_glmodel.reset();
|
m_shaded_preview_glmodel.reset();
|
||||||
m_paint_overlay_glmodel.reset();
|
m_paint_overlay_glmodel.reset();
|
||||||
m_paint_overlay_dirty = false;
|
m_paint_overlay_dirty = false;
|
||||||
m_painted_colors = PaintedColors{};
|
|
||||||
m_painted_colors_key.clear();
|
|
||||||
m_painted_colors_glmodel.reset();
|
|
||||||
m_painted_colors_drawn_key.clear();
|
|
||||||
m_painted_colors_runs.clear();
|
|
||||||
m_seed_fill_last_mesh_id = -1; // a hover from this session must not count in the next
|
|
||||||
// Any preview still in flight is superseded: raising the shared counter makes it abort at its next
|
// Any preview still in flight is superseded: raising the shared counter makes it abort at its next
|
||||||
// progress poll, and its completion handler then finds nothing to do.
|
// progress poll, and its completion handler then finds nothing to do.
|
||||||
m_preview_generation->fetch_add(1);
|
m_preview_generation->fetch_add(1);
|
||||||
m_preview_job_pending = false;
|
m_preview_job_pending = false;
|
||||||
// The palette caches hold the last volume's images; a closed gizmo should not keep them alive.
|
|
||||||
m_palette_cache.clear();
|
|
||||||
m_palette_quantizer = nullptr;
|
|
||||||
m_palette_pure_quantizer = nullptr;
|
|
||||||
m_palette_filaments.clear();
|
|
||||||
m_palette_images.clear();
|
|
||||||
m_mix_ranking.reset();
|
|
||||||
m_palette_slots.clear();
|
|
||||||
m_palette_changed = false;
|
|
||||||
m_uvcheck_glmodel.reset();
|
m_uvcheck_glmodel.reset();
|
||||||
m_wireframe_overlay_glmodel.reset();
|
m_wireframe_overlay_glmodel.reset();
|
||||||
m_wireframe_overlay_vcount = 0;
|
m_wireframe_overlay_vcount = 0;
|
||||||
@@ -592,17 +527,22 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
|
|||||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||||
|
|
||||||
// Once anything is painted, m_preview_glmodel holds the true displaced result (same algorithm
|
// Once anything is painted, m_preview_glmodel holds the true displaced result (same algorithm
|
||||||
// Bake uses). The untouched original topology (what render_triangles() draws) coincides exactly
|
// Bake uses). The untouched original topology (what render_triangles() draws) coincides
|
||||||
// with it everywhere except the displaced area, so both can be drawn: the real preview geometry
|
// exactly with it everywhere except the painted/displaced area, so both are drawn: the real
|
||||||
// first, then the selectors' highlight with a small depth bias so it wins the depth test on the
|
// preview geometry first, then the usual selection-highlight overlay with a small depth bias
|
||||||
// coincident surface. Where the surface has actually been displaced, the raised preview geometry
|
// so it wins the depth test on the coincident (unpainted) surface - keeping the familiar
|
||||||
// legitimately occludes the flat highlight - that visible relief is itself the "this is painted"
|
// enforcer/blocker highlight for precise brush editing there. Where the surface has actually
|
||||||
// indicator in that area.
|
// been displaced, the raised preview geometry legitimately occludes the flat overlay - that
|
||||||
|
// visible relief is itself the "this is painted" indicator in that area.
|
||||||
//
|
//
|
||||||
// The shaded preview never actually moves geometry (it only shades), so its depth is identical to
|
// The shaded preview is different: it never actually moves geometry (it only shades), so
|
||||||
// the highlight's *everywhere*: the depth-biased opaque highlight would win the depth test across the
|
// its depth is identical to the overlay's *everywhere*, not just in the unpainted area - the
|
||||||
// whole surface and hide the shading entirely. So render_triangles() leaves the textured volume out
|
// depth-biased opaque overlay would win the depth test across the whole surface and hide the relief
|
||||||
// there, and the translucent tint drawn further down is the paint feedback instead.
|
// shading entirely. So render_triangles() is skipped for it. What is *not* skipped is
|
||||||
|
// render_paint_overlay(): leaving the shading as the only paint feedback meant a stroke that
|
||||||
|
// erased paint, or added it with no texture picked, changed nothing on screen until the whole
|
||||||
|
// preview rebuilt at stroke end - and in the true-displacement view the opaque overlay is hidden
|
||||||
|
// by the raised surface for the same reason. The translucent tint covers both cases.
|
||||||
// Coalesced shaded-preview rebuild from an in-progress UV island drag (see on_island_edited): done here, at
|
// Coalesced shaded-preview rebuild from an in-progress UV island drag (see on_island_edited): done here, at
|
||||||
// most once per drawn frame, rather than synchronously in the UV canvas's mouse-move handler.
|
// most once per drawn frame, rather than synchronously in the UV canvas's mouse-move handler.
|
||||||
if (m_use_shaded_preview && m_shaded_preview_dirty) {
|
if (m_use_shaded_preview && m_shaded_preview_dirty) {
|
||||||
@@ -639,34 +579,24 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
|
|||||||
// Hide the real volume only when something is actually going to be drawn in its place; otherwise
|
// Hide the real volume only when something is actually going to be drawn in its place; otherwise
|
||||||
// put it back. Getting this wrong leaves an invisible model, so it is decided once, here, rather
|
// put it back. Getting this wrong leaves an invisible model, so it is decided once, here, rather
|
||||||
// than per branch below.
|
// than per branch below.
|
||||||
ModelVolume *mv = texture_volume();
|
|
||||||
m_parent.toggle_model_objects_visibility(true);
|
m_parent.toggle_model_objects_visibility(true);
|
||||||
if ((use_shaded || use_true_preview) && mv != nullptr)
|
if (use_shaded || use_true_preview) {
|
||||||
|
if (ModelVolume *mv = texture_volume())
|
||||||
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
|
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
|
||||||
m_c->selection_info()->get_active_instance(), mv);
|
m_c->selection_info()->get_active_instance(), mv);
|
||||||
|
}
|
||||||
|
|
||||||
// Over a preview, the paint feedback - the selectors' highlight in the Normal view, the tint in both -
|
|
||||||
// is drawn only while a mouse button is down: during a stroke, a fill click included. Between strokes
|
|
||||||
// it would cover the preview: the highlight buries it under a flat plane wherever the relief does not
|
|
||||||
// rise, and the tint washes it green. The debug view shows a captured stage rather than a preview, so
|
|
||||||
// it keeps the feedback.
|
|
||||||
const bool paint_feedback = is_painting() || m_debug_stage >= 0;
|
|
||||||
|
|
||||||
// Whether the textured volume's selector - and with it a fill tool's contour - is drawn this frame.
|
|
||||||
bool textured_selector_drawn = true;
|
|
||||||
if (use_shaded) {
|
if (use_shaded) {
|
||||||
render_shaded_preview_mesh();
|
render_shaded_preview_mesh();
|
||||||
// The shaded mesh is the textured volume alone, so the other model parts are still the selectors' to draw.
|
|
||||||
render_triangles(selection, mv);
|
|
||||||
textured_selector_drawn = false;
|
|
||||||
} else if (use_true_preview) {
|
} else if (use_true_preview) {
|
||||||
render_preview_mesh();
|
render_preview_mesh();
|
||||||
|
|
||||||
|
if (show_paint_overlay) {
|
||||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||||
glsafe(::glPolygonOffset(-1.0f, -1.0f));
|
glsafe(::glPolygonOffset(-1.0f, -1.0f));
|
||||||
// Without paint feedback, only the other model parts: render_preview_mesh() draws the textured one.
|
render_triangles(selection);
|
||||||
render_triangles(selection, paint_feedback ? nullptr : mv);
|
|
||||||
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
||||||
textured_selector_drawn = paint_feedback;
|
}
|
||||||
} else {
|
} else {
|
||||||
// render_triangles() *is* the model in a painter gizmo (it draws every model-part volume with the
|
// render_triangles() *is* the model in a painter gizmo (it draws every model-part volume with the
|
||||||
// selector's colours), not an overlay on top of one - so it still has to run under a UV-check
|
// selector's colours), not an overlay on top of one - so it still has to run under a UV-check
|
||||||
@@ -677,10 +607,6 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
|
|||||||
render_triangles(selection);
|
render_triangles(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The model's colour paint, over whichever surface was drawn, left out where that surface's preview shows
|
|
||||||
// paint of its own: every layer's for the Normal mesh, the active layer's otherwise.
|
|
||||||
const bool painted_colors_drawn = show_paint_overlay && m_debug_stage < 0 && render_painted_colors(use_true_preview);
|
|
||||||
|
|
||||||
// Every other layer's paint, in muted grey, so all layers stay visible while one of them is edited. Drawn
|
// Every other layer's paint, in muted grey, so all layers stay visible while one of them is edited. Drawn
|
||||||
// before the active layer's tint so that one reads on top where the two overlap.
|
// before the active layer's tint so that one reads on top where the two overlap.
|
||||||
if (show_paint_overlay)
|
if (show_paint_overlay)
|
||||||
@@ -689,32 +615,10 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
|
|||||||
// The translucent paint tint. Needed in the shaded view because the opaque highlight above is
|
// The translucent paint tint. Needed in the shaded view because the opaque highlight above is
|
||||||
// skipped there, and in the true-displacement view because the displaced surface rises *above*
|
// skipped there, and in the true-displacement view because the displaced surface rises *above*
|
||||||
// the undisplaced overlay geometry and hides it exactly where the relief is strongest - in both
|
// the undisplaced overlay geometry and hides it exactly where the relief is strongest - in both
|
||||||
// cases leaving an erase stroke with no visible effect until the next full preview rebuild. With no
|
// cases leaving an erase stroke with no visible effect until the next full preview rebuild.
|
||||||
// preview, the highlight is the selectors' own, except during a stroke over the colour paint: the paint
|
if (show_paint_overlay && (use_shaded || use_true_preview))
|
||||||
// there is not flushed yet, so the colours are still drawn over the stroke's highlight.
|
|
||||||
const bool preview_drawn = use_shaded || use_true_preview;
|
|
||||||
if (show_paint_overlay && (preview_drawn ? paint_feedback : painted_colors_drawn && is_painting()))
|
|
||||||
render_paint_overlay(m_paint_overlay_glmodel);
|
render_paint_overlay(m_paint_overlay_glmodel);
|
||||||
|
|
||||||
// A fill tool's contour is drawn with the selectors, under the colour paint drawn since - which covers it
|
|
||||||
// on a steep face, where the paint's slope-scaled offset outruns the contour's fixed one. Put it back on top,
|
|
||||||
// at the depth its first draw stored, hence LEQUAL. Only where the selector was drawn this frame: drawing it
|
|
||||||
// is what rebuilds the contour. The tool test matters because the base keeps the last hovered mesh when the
|
|
||||||
// tool changes.
|
|
||||||
const bool fill_tool = m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL ||
|
|
||||||
(m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER);
|
|
||||||
const int textured_mesh_id = texture_volume_raycaster_index();
|
|
||||||
if (painted_colors_drawn && textured_selector_drawn && fill_tool && textured_mesh_id >= 0 &&
|
|
||||||
textured_mesh_id == m_seed_fill_last_mesh_id && size_t(textured_mesh_id) < m_triangle_selectors.size()) {
|
|
||||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
|
||||||
GLint depth_func = GL_LESS;
|
|
||||||
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &depth_func));
|
|
||||||
glsafe(::glDepthFunc(GL_LEQUAL));
|
|
||||||
m_triangle_selectors[size_t(textured_mesh_id)]->render_paint_contour(
|
|
||||||
mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix());
|
|
||||||
glsafe(::glDepthFunc(GLenum(depth_func)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane
|
// The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane
|
||||||
// changes its selection in its own mouse handling, and a compare of a few ints per frame is free.
|
// changes its selection in its own mouse handling, and a compare of a few ints per frame is free.
|
||||||
{
|
{
|
||||||
@@ -1591,9 +1495,10 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
|
|||||||
shader->set_uniform("patch_center", m_shaded_patch_center);
|
shader->set_uniform("patch_center", m_shaded_patch_center);
|
||||||
shader->set_uniform("patch_axis", m_shaded_patch_axis);
|
shader->set_uniform("patch_axis", m_shaded_patch_axis);
|
||||||
|
|
||||||
// The printable palette, in RGB for display and in Lab for the match. Uploaded rather than matched
|
// The filament palette the mesh's per-triangle indices refer to. Count 0 means "no layer is
|
||||||
// on the CPU because the quantization is per fragment here. Count 0 means "no layer is colouring",
|
// colouring", and the shader keeps the model's own colour for every fragment.
|
||||||
// and the shader keeps the model's own colour for every fragment.
|
// The printable palette, in RGB for display and in Lab for the match. Uploaded rather than
|
||||||
|
// matched on the CPU because the quantization is per fragment here.
|
||||||
const GLTexture *color_tex = get_layer_color_texture(*layer);
|
const GLTexture *color_tex = get_layer_color_texture(*layer);
|
||||||
const int palette_count =
|
const int palette_count =
|
||||||
(color_tex != nullptr) ? int(std::min(m_shaded_preview_palette.size(), size_t(PALETTE_MAX_ENTRIES))) : 0;
|
(color_tex != nullptr) ? int(std::min(m_shaded_preview_palette.size(), size_t(PALETTE_MAX_ENTRIES))) : 0;
|
||||||
@@ -1601,17 +1506,24 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
|
|||||||
shader->set_uniform("has_color_tex", color_tex != nullptr);
|
shader->set_uniform("has_color_tex", color_tex != nullptr);
|
||||||
// A flat-colour image is matched against single filaments only, as the bake does.
|
// A flat-colour image is matched against single filaments only, as the bake does.
|
||||||
shader->set_uniform("pure_only", color_tex != nullptr && analyze_texture_detail(*layer).flat_colors);
|
shader->set_uniform("pure_only", color_tex != nullptr && analyze_texture_detail(*layer).flat_colors);
|
||||||
shader->set_uniform("prefer_pure_de", PREFER_PURE_DE);
|
|
||||||
for (int i = 0; i < palette_count; ++i) {
|
for (int i = 0; i < palette_count; ++i) {
|
||||||
const PaletteEntry &e = m_shaded_preview_palette[size_t(i)];
|
const PaletteEntry &e = m_shaded_preview_palette[size_t(i)];
|
||||||
const std::string idx = "[" + std::to_string(i) + "]";
|
const std::string idx = "[" + std::to_string(i) + "]";
|
||||||
// An entry's colour is what it prints as: its filament's, or for a mix its mixed filament slot's.
|
|
||||||
shader->set_uniform(("palette_rgb" + idx).c_str(), e.rgb);
|
shader->set_uniform(("palette_rgb" + idx).c_str(), e.rgb);
|
||||||
shader->set_uniform(("palette_lab" + idx).c_str(), srgb_to_lab(e.rgb));
|
shader->set_uniform(("palette_lab" + idx).c_str(), srgb_to_lab(e.rgb));
|
||||||
// Only so the shader can tell a mix (a != b) from a single filament.
|
// How the entry prints: its filament, or for a mix the two it interleaves and in what ratio.
|
||||||
shader->set_uniform(("palette_a" + idx).c_str(), e.a);
|
shader->set_uniform(("palette_a" + idx).c_str(), e.a);
|
||||||
shader->set_uniform(("palette_b" + idx).c_str(), e.b);
|
shader->set_uniform(("palette_b" + idx).c_str(), e.b);
|
||||||
}
|
}
|
||||||
|
// The filaments those indices refer to, and the interleave the shader resolves a mix with - the
|
||||||
|
// the mix's smooth average colour. m_palette_filaments is what m_shaded_preview_palette was built from.
|
||||||
|
const int filament_count =
|
||||||
|
(palette_count > 0) ? int(std::min(m_palette_filaments.size(), size_t(PALETTE_MAX_FILAMENTS))) : 0;
|
||||||
|
shader->set_uniform("filament_count", filament_count);
|
||||||
|
for (int i = 0; i < filament_count; ++i) {
|
||||||
|
const ColorRGBA &c = m_palette_filaments[size_t(i)];
|
||||||
|
shader->set_uniform(("filament_rgb[" + std::to_string(i) + "]").c_str(), Vec3f(c.r(), c.g(), c.b()));
|
||||||
|
}
|
||||||
if (color_tex != nullptr) {
|
if (color_tex != nullptr) {
|
||||||
shader->set_uniform("color_tex", 1);
|
shader->set_uniform("color_tex", 1);
|
||||||
glsafe(::glActiveTexture(GL_TEXTURE1));
|
glsafe(::glActiveTexture(GL_TEXTURE1));
|
||||||
@@ -1705,150 +1617,6 @@ void GLGizmoTextureDisplacement::rebuild_other_paint_overlay()
|
|||||||
m_other_paint_glmodel.set_color(ColorRGBA(0.55f, 0.58f, 0.60f, 0.35f));
|
m_other_paint_glmodel.set_color(ColorRGBA(0.55f, 0.58f, 0.60f, 0.35f));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> GLGizmoTextureDisplacement::PaintedColors::outside(const std::vector<bool> &excluded) const
|
|
||||||
{
|
|
||||||
std::vector<size_t> out;
|
|
||||||
out.reserve(source.size());
|
|
||||||
for (size_t i = 0; i < source.size(); ++i)
|
|
||||||
if (size_t(source[i]) >= excluded.size() || !excluded[size_t(source[i])])
|
|
||||||
out.push_back(i);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLGizmoTextureDisplacement::PaintedColors GLGizmoTextureDisplacement::painted_colors(const TriangleMesh &mesh,
|
|
||||||
const TriangleSelector::TriangleSplittingData &paint)
|
|
||||||
{
|
|
||||||
PaintedColors out;
|
|
||||||
TriangleSelector selector(mesh);
|
|
||||||
selector.deserialize(paint, false);
|
|
||||||
for (const EnforcerBlockerType state : TriangleSelector::extract_used_facet_states(paint)) {
|
|
||||||
if (state == EnforcerBlockerType::NONE)
|
|
||||||
continue;
|
|
||||||
std::vector<int> source;
|
|
||||||
const indexed_triangle_set part = selector.get_facets_strict(state, &source);
|
|
||||||
// Every state comes back over the same vertex array, only the triangles differ.
|
|
||||||
if (out.facets.vertices.empty())
|
|
||||||
out.facets.vertices = part.vertices;
|
|
||||||
out.facets.indices.insert(out.facets.indices.end(), part.indices.begin(), part.indices.end());
|
|
||||||
out.source.insert(out.source.end(), source.begin(), source.end());
|
|
||||||
out.state.resize(out.facets.indices.size(), int(state));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::rebuild_painted_colors(bool whole_stack)
|
|
||||||
{
|
|
||||||
const ModelVolume *mv = texture_volume();
|
|
||||||
const bool shown = mv != nullptr && any_layer_colors(*mv) && !mv->mmu_segmentation_facets.empty();
|
|
||||||
|
|
||||||
// The sub-triangles, keyed on what they were read from: the volume, its mesh and the paint.
|
|
||||||
std::string key;
|
|
||||||
if (shown)
|
|
||||||
key = std::to_string(mv->id().id) + ":" + std::to_string(reinterpret_cast<uintptr_t>(mv->mesh_ptr().get())) + ":" +
|
|
||||||
std::to_string(mv->mmu_segmentation_facets.timestamp());
|
|
||||||
if (key != m_painted_colors_key) {
|
|
||||||
m_painted_colors_key = std::move(key);
|
|
||||||
m_painted_colors = shown ? painted_colors(mv->mesh(), mv->mmu_segmentation_facets.get_data()) : PaintedColors{};
|
|
||||||
m_painted_colors_drawn_key.clear();
|
|
||||||
m_painted_colors_glmodel.reset();
|
|
||||||
m_painted_colors_runs.clear();
|
|
||||||
}
|
|
||||||
if (m_painted_colors.state.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The part drawn, keyed on whose paint is left out and on that paint.
|
|
||||||
std::string drawn_key = m_painted_colors_key + (whole_stack ? std::string(":all") : ":" + std::to_string(m_active_layer_slot));
|
|
||||||
for (const TextureDisplacementLayer &l : mv->texture_displacement_layers)
|
|
||||||
if (l.slot >= 0 && l.slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS))
|
|
||||||
drawn_key += "|" + std::to_string(l.slot) + "@" + std::to_string(mv->texture_displacement_facet(l.slot).timestamp());
|
|
||||||
if (drawn_key == m_painted_colors_drawn_key)
|
|
||||||
return;
|
|
||||||
m_painted_colors_drawn_key = std::move(drawn_key);
|
|
||||||
m_painted_colors_glmodel.reset();
|
|
||||||
m_painted_colors_runs.clear();
|
|
||||||
|
|
||||||
// Whole model triangles, as the facets record what they touch: a triangle the paint only partly covers
|
|
||||||
// is left to the preview.
|
|
||||||
std::vector<bool> excluded(mv->mesh().its.indices.size(), false);
|
|
||||||
for (const TextureDisplacementLayer &l : mv->texture_displacement_layers) {
|
|
||||||
if (l.slot < 0 || l.slot >= int(TEXTURE_DISPLACEMENT_MAX_LAYERS) || (!whole_stack && l.slot != m_active_layer_slot))
|
|
||||||
continue;
|
|
||||||
for (const TriangleSelector::TriangleBitStreamMapping &m : mv->texture_displacement_facet(l.slot).get_data().triangles_to_split)
|
|
||||||
if (m.triangle_idx >= 0 && size_t(m.triangle_idx) < excluded.size())
|
|
||||||
excluded[size_t(m.triangle_idx)] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// One model, its triangles in filament order (painted_colors() groups them), drawn a range per filament.
|
|
||||||
const std::vector<size_t> kept = m_painted_colors.outside(excluded);
|
|
||||||
GLModel::Geometry init_data;
|
|
||||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
|
|
||||||
init_data.reserve_vertices(kept.size() * 3);
|
|
||||||
init_data.reserve_indices(kept.size() * 3);
|
|
||||||
unsigned n = 0;
|
|
||||||
for (const size_t t : kept) {
|
|
||||||
const int state = m_painted_colors.state[t];
|
|
||||||
if (m_painted_colors_runs.empty() || m_painted_colors_runs.back().first != state)
|
|
||||||
m_painted_colors_runs.push_back({ state, { size_t(n), size_t(n) } });
|
|
||||||
for (int i = 0; i < 3; ++i)
|
|
||||||
init_data.add_vertex(m_painted_colors.facets.vertices[size_t(m_painted_colors.facets.indices[t][i])]);
|
|
||||||
init_data.add_triangle(n, n + 1, n + 2);
|
|
||||||
n += 3;
|
|
||||||
m_painted_colors_runs.back().second.second = size_t(n);
|
|
||||||
}
|
|
||||||
if (!init_data.is_empty())
|
|
||||||
m_painted_colors_glmodel.init_from(std::move(init_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLGizmoTextureDisplacement::render_painted_colors(bool whole_stack)
|
|
||||||
{
|
|
||||||
rebuild_painted_colors(whole_stack);
|
|
||||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
|
||||||
const ModelVolume *mv = texture_volume();
|
|
||||||
GLShaderProgram *shader = wxGetApp().get_shader("mm_gouraud");
|
|
||||||
if (mo == nullptr || mv == nullptr || shader == nullptr || !m_painted_colors_glmodel.is_initialized())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const Selection &selection = m_parent.get_selection();
|
|
||||||
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
|
|
||||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
|
||||||
const Transform3d &view_matrix = camera.get_view_matrix();
|
|
||||||
const Matrix3d normal_matrix = trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
|
||||||
const std::vector<ColorRGBA> colors = wxGetApp().plater()->get_extruders_colors();
|
|
||||||
|
|
||||||
shader->start_using();
|
|
||||||
// Set up as render_triangles() sets it up, so the colours are lit, clipped and slope-marked exactly as the
|
|
||||||
// neutral surface they cover.
|
|
||||||
const ClippingPlaneDataWrapper clp_data = get_clipping_plane_data();
|
|
||||||
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
|
|
||||||
shader->set_uniform("z_range", clp_data.z_range);
|
|
||||||
shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
|
|
||||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
|
||||||
shader->set_uniform("view_normal_matrix", Matrix3d(view_matrix.matrix().block(0, 0, 3, 3) * normal_matrix));
|
|
||||||
shader->set_uniform("volume_world_matrix", trafo_matrix);
|
|
||||||
shader->set_uniform("volume_mirrored", trafo_matrix.matrix().determinant() < 0.);
|
|
||||||
shader->set_uniform("slope.actived", m_parent.is_using_slope());
|
|
||||||
shader->set_uniform("slope.volume_world_normal_matrix", Matrix3f(normal_matrix.cast<float>()));
|
|
||||||
shader->set_uniform("slope.normal_z", float(-std::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg))));
|
|
||||||
shader->set_uniform("slope.up_direction", get_tilt_up_direction());
|
|
||||||
shader->set_uniform("show_wireframe", false);
|
|
||||||
// Pulled forward and without depth writes, as the tint is (see render_paint_overlay()). The tint is
|
|
||||||
// drawn after this, so it shows on top.
|
|
||||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
|
||||||
glsafe(::glPolygonOffset(-2.f, -2.f));
|
|
||||||
glsafe(::glDepthMask(GL_FALSE));
|
|
||||||
bool drawn = false;
|
|
||||||
for (const auto &[state, range] : m_painted_colors_runs)
|
|
||||||
if (state >= 1 && size_t(state) <= colors.size()) {
|
|
||||||
m_painted_colors_glmodel.set_color(adjust_color_for_rendering(colors[size_t(state - 1)]));
|
|
||||||
m_painted_colors_glmodel.render(range, shader);
|
|
||||||
drawn = true;
|
|
||||||
}
|
|
||||||
glsafe(::glDepthMask(GL_TRUE));
|
|
||||||
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
|
||||||
shader->stop_using();
|
|
||||||
return drawn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::rebuild_paint_overlay()
|
void GLGizmoTextureDisplacement::rebuild_paint_overlay()
|
||||||
{
|
{
|
||||||
m_paint_overlay_glmodel.reset();
|
m_paint_overlay_glmodel.reset();
|
||||||
@@ -1903,12 +1671,11 @@ void GLGizmoTextureDisplacement::render_paint_overlay(GLModel &overlay)
|
|||||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo_matrix);
|
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo_matrix);
|
||||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||||
// Translucent, and pulled toward the camera so it wins the depth test against the coincident
|
// Translucent, and pulled toward the camera so it wins the depth test against the coincident
|
||||||
// shaded surface - and against the selectors' highlight at -1 in the Normal view, by the full depth
|
// shaded surface. Depth writes are off: this is a tint, and letting it own the depth buffer would
|
||||||
// unit OpenGL guarantees to tell apart. Depth writes are off: this is a tint, and letting it own the
|
// make the wireframe and seam overlays drawn after it fight with geometry that is not really
|
||||||
// depth buffer would make the wireframe and seam overlays drawn after it fight with geometry that is
|
// there. Blending is already enabled by render_painter_gizmo().
|
||||||
// not really there. Blending is already enabled by render_painter_gizmo().
|
|
||||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||||
glsafe(::glPolygonOffset(-2.f, -2.f));
|
glsafe(::glPolygonOffset(-1.5f, -1.5f));
|
||||||
glsafe(::glDepthMask(GL_FALSE));
|
glsafe(::glDepthMask(GL_FALSE));
|
||||||
overlay.render();
|
overlay.render();
|
||||||
glsafe(::glDepthMask(GL_TRUE));
|
glsafe(::glDepthMask(GL_TRUE));
|
||||||
@@ -2224,9 +1991,6 @@ void GLGizmoTextureDisplacement::rebuild_preview()
|
|||||||
// finishes after the job queued below - and, since the counter is shared with the worker, that
|
// finishes after the job queued below - and, since the counter is shared with the worker, that
|
||||||
// job also notices mid-run and aborts rather than computing a result nobody will use.
|
// job also notices mid-run and aborts rather than computing a result nobody will use.
|
||||||
m_preview_generation->fetch_add(1);
|
m_preview_generation->fetch_add(1);
|
||||||
// Everything rebuilt from here on reads the current palette. Cleared before any of the early returns
|
|
||||||
// below, which would otherwise leave it set and re-run this every frame.
|
|
||||||
m_palette_changed = false;
|
|
||||||
update_uv_editor();
|
update_uv_editor();
|
||||||
rebuild_shaded_preview_mesh();
|
rebuild_shaded_preview_mesh();
|
||||||
rebuild_paint_overlay();
|
rebuild_paint_overlay();
|
||||||
@@ -2297,21 +2061,25 @@ void GLGizmoTextureDisplacement::queue_preview_job()
|
|||||||
input.volume_to_world = texture_displacement_volume_to_world(*mv);
|
input.volume_to_world = texture_displacement_volume_to_world(*mv);
|
||||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||||
input.facets_data[size_t(i)] = mv->texture_displacement_facet(i).get_data();
|
input.facets_data[size_t(i)] = mv->texture_displacement_facet(i).get_data();
|
||||||
// Captured here rather than read in the handler: the palette is main-thread state, and the preview
|
// Captured here rather than read in the handler: get_extruders_colors() is main-thread state and
|
||||||
// has to be grouped against the same palette it was computed with, not whatever it is by the time
|
// the preview has to be grouped against the same palette it was computed with, not whatever is
|
||||||
// the result lands.
|
// loaded by the time it lands.
|
||||||
input.color = color_settings_for(*mv);
|
input.color = color_settings_for(*mv);
|
||||||
// The result names a palette entry per triangle (index + 1), so these are the colours to draw it in:
|
// The filament list the result's indices refer to, captured with the job rather than read back
|
||||||
// a single filament's own, or for a mix the colour its slot will show once a bake creates it.
|
// when it lands - loading a filament meanwhile must not recolour a preview computed against a
|
||||||
std::vector<ColorRGBA> entry_colors;
|
// different list.
|
||||||
entry_colors.reserve(input.color.palette.size());
|
// Every extruder, not the palette's physical-only list: the bake writes the filament it resolved
|
||||||
for (const PrintableColor &e : input.color.palette)
|
// to, and a mix resolves to a *mixed filament slot*, which is an extruder past the physical ones.
|
||||||
entry_colors.emplace_back(e.rgb.x(), e.rgb.y(), e.rgb.z(), 1.f);
|
// Grouping against the shorter list dropped every triangle carrying such a slot out of the mesh
|
||||||
|
// entirely - the relief vanished and left only the few triangles that happened to print in a plain
|
||||||
|
// filament. The palette still has to be built from physical filaments alone (see filament_palette()),
|
||||||
|
// which is why these two are not the same list.
|
||||||
|
const std::vector<ColorRGBA> filaments = wxGetApp().plater()->get_extruders_colors();
|
||||||
|
|
||||||
m_preview_job_running = true;
|
m_preview_job_running = true;
|
||||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||||
queue_job(worker, std::make_unique<TextureDisplacementPreviewJob>(std::move(input), generation, m_preview_generation,
|
queue_job(worker, std::make_unique<TextureDisplacementPreviewJob>(std::move(input), generation, m_preview_generation,
|
||||||
[this, entry_colors](TextureDisplacementPreviewResult result, uint64_t result_generation) {
|
[this, filaments](TextureDisplacementPreviewResult result, uint64_t result_generation) {
|
||||||
indexed_triangle_set its = std::move(result.mesh);
|
indexed_triangle_set its = std::move(result.mesh);
|
||||||
m_preview_job_running = false;
|
m_preview_job_running = false;
|
||||||
if (result_generation != m_preview_generation->load()) {
|
if (result_generation != m_preview_generation->load()) {
|
||||||
@@ -2325,11 +2093,14 @@ void GLGizmoTextureDisplacement::queue_preview_job()
|
|||||||
} else {
|
} else {
|
||||||
m_preview_glmodel.reset();
|
m_preview_glmodel.reset();
|
||||||
m_preview_color_runs.clear();
|
m_preview_color_runs.clear();
|
||||||
if (result.triangle_color.size() == its.indices.size() && !entry_colors.empty()) {
|
if (result.triangle_color.size() == its.indices.size() && !filaments.empty()) {
|
||||||
|
// Group by *filament*, not by palette entry: what the bake wrote is the resolved
|
||||||
|
// filament, interleaving already applied, so this shows the real banding rather
|
||||||
|
// than the flat average the eye will turn it into.
|
||||||
indexed_triangle_set sorted;
|
indexed_triangle_set sorted;
|
||||||
sorted.vertices = its.vertices;
|
sorted.vertices = its.vertices;
|
||||||
sorted.indices.reserve(its.indices.size());
|
sorted.indices.reserve(its.indices.size());
|
||||||
for (int want = 0; want <= int(entry_colors.size()); ++want) {
|
for (int want = 0; want <= int(filaments.size()); ++want) {
|
||||||
const size_t first = sorted.indices.size();
|
const size_t first = sorted.indices.size();
|
||||||
for (size_t i = 0; i < its.indices.size(); ++i)
|
for (size_t i = 0; i < its.indices.size(); ++i)
|
||||||
if (int(result.triangle_color[i]) == want)
|
if (int(result.triangle_color[i]) == want)
|
||||||
@@ -2338,7 +2109,7 @@ void GLGizmoTextureDisplacement::queue_preview_job()
|
|||||||
continue;
|
continue;
|
||||||
m_preview_color_runs.push_back(
|
m_preview_color_runs.push_back(
|
||||||
{ { first * 3, sorted.indices.size() * 3 },
|
{ { first * 3, sorted.indices.size() * 3 },
|
||||||
want == 0 ? GLVolume::NEUTRAL_COLOR : entry_colors[size_t(want - 1)] });
|
want == 0 ? GLVolume::NEUTRAL_COLOR : filaments[size_t(want - 1)] });
|
||||||
}
|
}
|
||||||
m_preview_glmodel.init_from(sorted);
|
m_preview_glmodel.init_from(sorted);
|
||||||
} else {
|
} else {
|
||||||
@@ -3595,9 +3366,6 @@ void GLGizmoTextureDisplacement::update_from_model_object(bool first_update)
|
|||||||
|
|
||||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||||
m_triangle_selectors.clear();
|
m_triangle_selectors.clear();
|
||||||
// The base keeps the last mesh a fill tool hovered, and render_painter_gizmo() reads it as a hover that is
|
|
||||||
// still on: a new set of selectors has none.
|
|
||||||
m_seed_fill_last_mesh_id = -1;
|
|
||||||
|
|
||||||
std::vector<ColorRGBA> ebt_colors;
|
std::vector<ColorRGBA> ebt_colors;
|
||||||
ebt_colors.push_back(GLVolume::NEUTRAL_COLOR);
|
ebt_colors.push_back(GLVolume::NEUTRAL_COLOR);
|
||||||
@@ -4498,101 +4266,84 @@ TextureDisplacementFacetsData GLGizmoTextureDisplacement::facets_data_of(const M
|
|||||||
bool GLGizmoTextureDisplacement::any_layer_colors(const ModelVolume &mv)
|
bool GLGizmoTextureDisplacement::any_layer_colors(const ModelVolume &mv)
|
||||||
{
|
{
|
||||||
for (const TextureDisplacementLayer &layer : mv.texture_displacement_layers)
|
for (const TextureDisplacementLayer &layer : mv.texture_displacement_layers)
|
||||||
if (layer.color_enabled && !layer.empty() && height_texture_has_color(layer))
|
if (layer.color_enabled && !layer.empty() && decode_height_texture(layer).has_color())
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLGizmoTextureDisplacement::bind_mixes_to_filament_slots(std::vector<PaletteEntry> &palette)
|
||||||
|
{
|
||||||
|
Sidebar *sidebar = &wxGetApp().plater()->sidebar();
|
||||||
|
if (sidebar == nullptr)
|
||||||
|
return;
|
||||||
|
for (PaletteEntry &e : palette) {
|
||||||
|
if (!e.is_mix())
|
||||||
|
continue;
|
||||||
|
// Components are 1-based in the config; the ratios are percentages summing to 100, which is the
|
||||||
|
// form create_mixed_filament_from_result() normalises from.
|
||||||
|
const int a_pct = int(std::lround(100.0 * double(e.num) / double(e.den)));
|
||||||
|
const int slot = sidebar->ensure_mixed_filament({ unsigned(e.a + 1), unsigned(e.b + 1) },
|
||||||
|
{ a_pct, 100 - a_pct });
|
||||||
|
if (slot >= 0) {
|
||||||
|
e.a = e.b = slot;
|
||||||
|
e.num = e.den = 1;
|
||||||
|
} else {
|
||||||
|
// No room for another slot. Collapse to the component that dominates the blend, which is what
|
||||||
|
// the old per-triangle path did on a surface it could not band anyway.
|
||||||
|
const int dominant = (e.num * 2 >= e.den) ? e.a : e.b;
|
||||||
|
e.a = e.b = dominant;
|
||||||
|
e.num = e.den = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextureColorSettings GLGizmoTextureDisplacement::color_settings_for(const ModelVolume &mv)
|
TextureColorSettings GLGizmoTextureDisplacement::color_settings_for(const ModelVolume &mv)
|
||||||
{
|
{
|
||||||
TextureColorSettings out;
|
TextureColorSettings out;
|
||||||
if (!any_layer_colors(mv))
|
if (!any_layer_colors(mv))
|
||||||
return out; // nothing is colouring: every colour path stays switched off
|
return out; // nothing is colouring: every colour path stays switched off
|
||||||
out.palette = cached_palette();
|
out.palette = cached_palette();
|
||||||
out.palette_pure = make_palette(m_palette_filaments, {});
|
out.palette_pure = make_palette(m_palette_filaments, /* mixing */ false, PALETTE_MAX_ENTRIES);
|
||||||
|
// Done here rather than in cached_palette(): this runs when a preview or a bake is queued, off a
|
||||||
|
// user action, while that one is also touched from the render path - and creating filament slots
|
||||||
|
// there would mutate the project mid-frame.
|
||||||
|
bind_mixes_to_filament_slots(out.palette);
|
||||||
out.despeckle_passes = mv.texture_displacement_options.color_despeckle;
|
out.despeckle_passes = mv.texture_displacement_options.color_despeckle;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GLGizmoTextureDisplacement::PaletteEntry> &GLGizmoTextureDisplacement::cached_palette()
|
const std::vector<GLGizmoTextureDisplacement::PaletteEntry> &GLGizmoTextureDisplacement::cached_palette()
|
||||||
{
|
{
|
||||||
// Rebuilt only when something it depends on actually changes. The shaded preview rebuilds on every
|
// Rebuilt only when the loaded filaments or the mixing setting actually change. The shaded preview
|
||||||
// paint stroke and the panel asks every frame, while ranking the mixes and filling the quantizer's
|
// rebuilds on every paint stroke and the subdivide preview on every slider frame, and filling the
|
||||||
// lookup cube each take tens of milliseconds - paying that per stroke is the difference between
|
// quantizer's lookup cube for a 64-entry palette is tens of milliseconds - paying that per stroke
|
||||||
// painting that keeps up and painting that stutters.
|
// is the difference between painting that keeps up and painting that stutters.
|
||||||
//
|
|
||||||
// Two levels. The ranking depends only on the filaments and the images, so dragging the count, or a
|
|
||||||
// bake creating slots, re-picks from it without ranking again; the palette and its quantizers depend
|
|
||||||
// on that pick as well.
|
|
||||||
const ModelVolume *mv = texture_volume();
|
const ModelVolume *mv = texture_volume();
|
||||||
const bool mixing = mv != nullptr && mv->texture_displacement_options.color_mix_enabled;
|
const bool mixing = mv != nullptr && mv->texture_displacement_options.color_mix_enabled;
|
||||||
const int mix_count = mv != nullptr ? std::max(0, mv->texture_displacement_options.color_mix_count) : 0;
|
|
||||||
std::vector<ColorRGBA> filaments = filament_palette();
|
std::vector<ColorRGBA> filaments = filament_palette();
|
||||||
// The images themselves rather than their addresses, so a freed and reallocated image can never
|
// Every mix costs a filament slot once they are bound to one, and the mask can name only so many
|
||||||
// pass for the old one. Whether one has colour, and whether its colours are flat, follows from it.
|
// states, so the palette has to leave room beside the physical filaments it already counts.
|
||||||
std::vector<std::shared_ptr<std::vector<unsigned char>>> images;
|
const int cap = int(EnforcerBlockerType::ExtruderMax);
|
||||||
if (mv != nullptr)
|
if (m_palette_cache.empty() || filaments != m_palette_filaments || mixing != m_palette_mixing ||
|
||||||
for (const TextureDisplacementLayer &layer : mv->texture_displacement_layers)
|
cap != m_palette_cap) {
|
||||||
if (layer.color_enabled && !layer.empty())
|
|
||||||
images.push_back(layer.image_data);
|
|
||||||
|
|
||||||
const bool ranking_stale = filaments != m_palette_filaments || images != m_palette_images;
|
|
||||||
if (ranking_stale) {
|
|
||||||
m_palette_filaments = std::move(filaments);
|
m_palette_filaments = std::move(filaments);
|
||||||
m_palette_images = std::move(images);
|
|
||||||
m_mix_ranking.reset();
|
|
||||||
}
|
|
||||||
if (mixing && !m_mix_ranking)
|
|
||||||
m_mix_ranking = rank_mixes(m_palette_filaments, mix_targets(mv->texture_displacement_layers),
|
|
||||||
int(EnforcerBlockerType::ExtruderMax) - int(m_palette_filaments.size()));
|
|
||||||
|
|
||||||
// Which mixes the project can still print: those it has a fixed slot for, plus as many new ones as
|
|
||||||
// there are free slots.
|
|
||||||
const PresetBundle &bundle = *wxGetApp().preset_bundle;
|
|
||||||
const int free_slots = std::max(0, int(EnforcerBlockerType::ExtruderMax) - int(bundle.filament_presets.size()));
|
|
||||||
const std::vector<std::string> slots = mixed_slot_signature(bundle.project_config);
|
|
||||||
if (ranking_stale || m_palette_cache.empty() || mixing != m_palette_mixing || mix_count != m_palette_mix_count ||
|
|
||||||
free_slots != m_palette_free_slots || slots != m_palette_slots) {
|
|
||||||
m_palette_mixing = mixing;
|
m_palette_mixing = mixing;
|
||||||
m_palette_mix_count = mix_count;
|
m_palette_cap = cap;
|
||||||
m_palette_free_slots = free_slots;
|
m_palette_cache = make_palette(m_palette_filaments, mixing, cap);
|
||||||
m_palette_slots = slots;
|
m_palette_quantizer = make_palette_quantizer(m_palette_cache);
|
||||||
std::vector<PaletteEntry> mixes;
|
|
||||||
if (mixing && m_mix_ranking)
|
|
||||||
mixes = pick_mixes(*m_mix_ranking, mix_count, free_slots, [&bundle](const PaletteEntry &e) {
|
|
||||||
return find_fixed_mixed_filament(bundle.project_config, {unsigned(e.a + 1), unsigned(e.b + 1)},
|
|
||||||
{e.a_percent(), 100 - e.a_percent()}) >= 0;
|
|
||||||
});
|
|
||||||
std::vector<PaletteEntry> palette = make_palette(m_palette_filaments, mixes);
|
|
||||||
// The previews keep what they were drawn with, so a palette that really changed under them -
|
|
||||||
// a filament or a mixed slot edited in the sidebar - has to send them round again.
|
|
||||||
m_palette_changed = m_palette_changed || (!m_palette_cache.empty() && !same_palette(palette, m_palette_cache));
|
|
||||||
m_palette_cache = std::move(palette);
|
|
||||||
m_palette_quantizer = nullptr;
|
|
||||||
m_palette_pure_quantizer = nullptr;
|
|
||||||
}
|
}
|
||||||
return m_palette_cache;
|
return m_palette_cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<ColorQuantizeFn, ColorQuantizeFn> GLGizmoTextureDisplacement::palette_quantizers()
|
|
||||||
{
|
|
||||||
cached_palette();
|
|
||||||
if (!m_palette_quantizer) {
|
|
||||||
m_palette_quantizer = make_palette_quantizer(m_palette_cache);
|
|
||||||
const bool has_mixes = m_palette_cache.size() > m_palette_filaments.size();
|
|
||||||
m_palette_pure_quantizer = has_mixes ? make_palette_quantizer(make_palette(m_palette_filaments, {})) : m_palette_quantizer;
|
|
||||||
}
|
|
||||||
return { m_palette_quantizer, m_palette_pure_quantizer };
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ColorRGBA> GLGizmoTextureDisplacement::filament_palette()
|
std::vector<ColorRGBA> GLGizmoTextureDisplacement::filament_palette()
|
||||||
{
|
{
|
||||||
std::vector<ColorRGBA> all = wxGetApp().plater()->get_extruders_colors();
|
std::vector<ColorRGBA> all = wxGetApp().plater()->get_extruders_colors();
|
||||||
|
|
||||||
// Physical filaments only. A mix bakes into a mixed filament slot of its own, and those slots are
|
// Physical filaments only. The mixes this palette produces each become a mixed filament slot of
|
||||||
// extruders too - mixing them again would hand a blend components naming a virtual slot, where it
|
// their own (see bind_mixes_to_filament_slots()), and those slots are extruders too - so taking the
|
||||||
// can only name physical ones ("Mixed filament has invalid or mismatched components"). Mixed slots
|
// list as it comes meant the next rebuild mixed *them* again, and handed components naming a
|
||||||
// are kept after the physical ones, so filament i here is extruder i.
|
// virtual slot to a blend that can only name physical ones. That is what left entries reading
|
||||||
|
// "filament 1 plus nothing" and raised "Mixed filament has invalid or mismatched components".
|
||||||
const auto *is_mixed = wxGetApp().preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed");
|
const auto *is_mixed = wxGetApp().preset_bundle->project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||||
std::vector<ColorRGBA> palette;
|
std::vector<ColorRGBA> palette;
|
||||||
palette.reserve(all.size());
|
palette.reserve(all.size());
|
||||||
@@ -4606,209 +4357,42 @@ std::vector<ColorRGBA> GLGizmoTextureDisplacement::filament_palette()
|
|||||||
return palette;
|
return palette;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GLGizmoTextureDisplacement::MixTarget> GLGizmoTextureDisplacement::mix_targets(
|
|
||||||
const std::vector<TextureDisplacementLayer> &layers)
|
|
||||||
{
|
|
||||||
// Each layer's bins, weighted by their share of that layer's pixels.
|
|
||||||
std::vector<std::vector<MixTarget>> per_layer;
|
|
||||||
for (const TextureDisplacementLayer &layer : layers) {
|
|
||||||
if (!layer.color_enabled || layer.empty() || analyze_texture_detail(layer).flat_colors)
|
|
||||||
continue;
|
|
||||||
TextureDisplacementLayer raw = layer;
|
|
||||||
raw.smoothing = 0.f;
|
|
||||||
const DecodedHeightTexture tex = decode_height_texture(raw);
|
|
||||||
if (!tex.has_color())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// 16 levels per channel, each bin keeping the mean of the colours that fell in it: a coarse grid
|
|
||||||
// to gather on, without snapping every colour to a bin corner.
|
|
||||||
struct Bin
|
|
||||||
{
|
|
||||||
double r = 0., g = 0., b = 0.;
|
|
||||||
uint32_t n = 0;
|
|
||||||
};
|
|
||||||
std::vector<Bin> bins(size_t(16 * 16 * 16));
|
|
||||||
// Past the budget, one pixel from each run of `stride`, at an offset jittered by a fixed-seed
|
|
||||||
// generator. A fixed step would sample a lattice that a striped texture can line up with, so
|
|
||||||
// that only one of its colours is ever seen; jittered, stripes of any period or orientation are
|
|
||||||
// sampled in proportion, and the same image still always gives the same targets. The offset
|
|
||||||
// comes from the generator's high bits: a power-of-two LCG's low bits repeat every 2, 4, 8...
|
|
||||||
// steps, and the stride is a power of two for exactly the images large enough to need this.
|
|
||||||
const size_t npx = size_t(tex.width) * size_t(tex.height);
|
|
||||||
const size_t stride = std::max<size_t>(1, npx / MIX_TARGET_SAMPLES);
|
|
||||||
uint64_t state = 0x9E3779B97F4A7C15ull;
|
|
||||||
size_t sampled = 0;
|
|
||||||
for (size_t start = 0; start < npx; start += stride) {
|
|
||||||
state = state * 6364136223846793005ull + 1442695040888963407ull;
|
|
||||||
const size_t i = start + size_t((uint64_t(uint32_t(state >> 32)) * uint64_t(stride)) >> 32);
|
|
||||||
if (i >= npx)
|
|
||||||
break;
|
|
||||||
const uint8_t *px = &tex.rgb[i * 3];
|
|
||||||
Bin &bin = bins[size_t(px[0] >> 4) * 256 + size_t(px[1] >> 4) * 16 + size_t(px[2] >> 4)];
|
|
||||||
bin.r += px[0];
|
|
||||||
bin.g += px[1];
|
|
||||||
bin.b += px[2];
|
|
||||||
++bin.n;
|
|
||||||
++sampled;
|
|
||||||
}
|
|
||||||
std::vector<MixTarget> targets;
|
|
||||||
for (const Bin &bin : bins)
|
|
||||||
if (bin.n > 0) {
|
|
||||||
const double inv = 1. / (255. * double(bin.n));
|
|
||||||
targets.push_back({ srgb_to_lab(Vec3f(float(bin.r * inv), float(bin.g * inv), float(bin.b * inv))),
|
|
||||||
float(double(bin.n) / double(sampled)) });
|
|
||||||
}
|
|
||||||
per_layer.push_back(std::move(targets));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The layers weigh the same and together 1, settled before the pruning below: what rank_mixes() sums
|
|
||||||
// over the kept bins is then a mean over every pixel of the colouring layers, with the pixels of a
|
|
||||||
// dropped bin counted as no better off.
|
|
||||||
std::vector<MixTarget> out;
|
|
||||||
for (std::vector<MixTarget> &targets : per_layer)
|
|
||||||
for (MixTarget &t : targets) {
|
|
||||||
t.weight /= float(per_layer.size());
|
|
||||||
out.push_back(t);
|
|
||||||
}
|
|
||||||
if (out.size() > MIX_TARGET_BINS) {
|
|
||||||
std::partial_sort(out.begin(), out.begin() + MIX_TARGET_BINS, out.end(),
|
|
||||||
[](const MixTarget &l, const MixTarget &r) { return l.weight > r.weight; });
|
|
||||||
out.resize(MIX_TARGET_BINS);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement::rank_mixes(
|
|
||||||
const std::vector<ColorRGBA> &filaments, const std::vector<MixTarget> &targets, int limit)
|
|
||||||
{
|
|
||||||
const int n = int(filaments.size());
|
|
||||||
if (n < 2 || targets.empty() || limit <= 0)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
// Every pair at every short-cycle ratio, coloured as its slot will be.
|
|
||||||
std::vector<std::string> hex(filaments.size());
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
hex[size_t(i)] = encode_color(filaments[size_t(i)]);
|
|
||||||
std::vector<PaletteEntry> candidates;
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
for (int j = i + 1; j < n; ++j)
|
|
||||||
for (int den = 2; den <= 6; ++den)
|
|
||||||
for (int num = 1; num < den; ++num) {
|
|
||||||
if (std::gcd(num, den) != 1)
|
|
||||||
continue; // 2/4 is 1/2, already there
|
|
||||||
PaletteEntry e{ Vec3f::Zero(), i, j, num, den };
|
|
||||||
ColorRGB blended;
|
|
||||||
if (!decode_color(blend_color_multi({ hex[size_t(i)], hex[size_t(j)] },
|
|
||||||
{ e.a_percent(), 100 - e.a_percent() }),
|
|
||||||
blended))
|
|
||||||
continue;
|
|
||||||
e.rgb = Vec3f(blended.r(), blended.g(), blended.b());
|
|
||||||
candidates.push_back(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// How far each target is from the nearest single filament, and from every candidate.
|
|
||||||
const size_t nt = targets.size(), nc = candidates.size();
|
|
||||||
std::vector<Vec3f> filament_lab(size_t(n), Vec3f::Zero());
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
filament_lab[size_t(i)] = srgb_to_lab(Vec3f(filaments[size_t(i)].r(), filaments[size_t(i)].g(), filaments[size_t(i)].b()));
|
|
||||||
const auto de = [](const Vec3f &l, const Vec3f &r) { return DeltaE00(l.x(), l.y(), l.z(), r.x(), r.y(), r.z()); };
|
|
||||||
std::vector<float> pure_d(nt, std::numeric_limits<float>::max());
|
|
||||||
for (size_t t = 0; t < nt; ++t)
|
|
||||||
for (const Vec3f &lab : filament_lab)
|
|
||||||
pure_d[t] = std::min(pure_d[t], de(targets[t].lab, lab));
|
|
||||||
std::vector<float> dist(nc * nt);
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, nc), [&](const tbb::blocked_range<size_t> &range) {
|
|
||||||
for (size_t c = range.begin(); c < range.end(); ++c) {
|
|
||||||
const Vec3f lab = srgb_to_lab(candidates[c].rgb);
|
|
||||||
for (size_t t = 0; t < nt; ++t)
|
|
||||||
dist[c * nt + t] = de(targets[t].lab, lab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Greedy: each round takes the candidate that lowers the weighted error the most. A candidate only
|
|
||||||
// counts where it beats the single filament by PREFER_PURE_DE, since everywhere else the quantizer
|
|
||||||
// picks the filament anyway.
|
|
||||||
std::vector<float> current = pure_d;
|
|
||||||
std::vector<char> taken(nc, 0);
|
|
||||||
std::vector<PaletteEntry> out;
|
|
||||||
const auto counts = [&](size_t c, size_t t) {
|
|
||||||
const float d = dist[c * nt + t];
|
|
||||||
return d < current[t] && d <= pure_d[t] - PREFER_PURE_DE;
|
|
||||||
};
|
|
||||||
while (int(out.size()) < limit) {
|
|
||||||
size_t best = nc;
|
|
||||||
double best_gain = 0.;
|
|
||||||
for (size_t c = 0; c < nc; ++c) {
|
|
||||||
if (taken[c])
|
|
||||||
continue;
|
|
||||||
double gain = 0.;
|
|
||||||
for (size_t t = 0; t < nt; ++t)
|
|
||||||
if (counts(c, t))
|
|
||||||
gain += double(targets[t].weight) * double(current[t] - dist[c * nt + t]);
|
|
||||||
if (gain > best_gain) {
|
|
||||||
best_gain = gain;
|
|
||||||
best = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (best == nc || best_gain < double(MIN_MIX_GAIN))
|
|
||||||
break;
|
|
||||||
taken[best] = 1;
|
|
||||||
out.push_back(candidates[best]);
|
|
||||||
for (size_t t = 0; t < nt; ++t)
|
|
||||||
if (counts(best, t))
|
|
||||||
current[t] = dist[best * nt + t];
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement::pick_mixes(
|
|
||||||
const std::vector<PaletteEntry> &ranking, int count, int free_slots, const std::function<bool(const PaletteEntry &)> &reusable)
|
|
||||||
{
|
|
||||||
std::vector<PaletteEntry> out;
|
|
||||||
for (const PaletteEntry &e : ranking) {
|
|
||||||
if (int(out.size()) >= count)
|
|
||||||
break;
|
|
||||||
// Out of free slots, a later mix that already has one still fits.
|
|
||||||
if (reusable && reusable(e)) {
|
|
||||||
out.push_back(e);
|
|
||||||
} else if (free_slots > 0) {
|
|
||||||
out.push_back(e);
|
|
||||||
--free_slots;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement::make_palette(
|
std::vector<GLGizmoTextureDisplacement::PaletteEntry> GLGizmoTextureDisplacement::make_palette(
|
||||||
const std::vector<ColorRGBA> &filaments, const std::vector<PaletteEntry> &mixes)
|
const std::vector<ColorRGBA> &filaments, bool mixing, int max_entries)
|
||||||
{
|
{
|
||||||
std::vector<PaletteEntry> out;
|
std::vector<PaletteEntry> out;
|
||||||
out.reserve(filaments.size() + mixes.size());
|
const int n = int(filaments.size());
|
||||||
for (int i = 0; i < int(filaments.size()); ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
out.push_back({ Vec3f(filaments[size_t(i)].r(), filaments[size_t(i)].g(), filaments[size_t(i)].b()), i, i, 1, 1 });
|
out.push_back({ Vec3f(filaments[size_t(i)].r(), filaments[size_t(i)].g(), filaments[size_t(i)].b()),
|
||||||
out.insert(out.end(), mixes.begin(), mixes.end());
|
i, i, 1, 1 });
|
||||||
|
if (!mixing || n < 2)
|
||||||
return out;
|
return out;
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> GLGizmoTextureDisplacement::palette_filaments(const std::vector<PaletteEntry> &palette,
|
// How many intermediate steps each pair gets, chosen so the whole palette stays under
|
||||||
const std::vector<uint8_t> &triangle_color,
|
// PALETTE_MAX_ENTRIES. Fewer filaments means more room for mixes, which is also what you want:
|
||||||
const std::function<int(const PaletteEntry &)> &slot_for_mix)
|
// with two filaments the mixes are the only way to get anywhere, and with sixteen there is little
|
||||||
{
|
// point mixing at all. `den` is also the band/dither repeat, so a small one is a short pattern.
|
||||||
std::vector<char> used(palette.size(), 0);
|
const int pairs = n * (n - 1) / 2;
|
||||||
for (const uint8_t v : triangle_color)
|
int steps = 0;
|
||||||
if (v > 0 && size_t(v) <= palette.size())
|
for (int s = 5; s >= 1; --s)
|
||||||
used[size_t(v) - 1] = 1;
|
if (n + pairs * s <= max_entries) {
|
||||||
|
steps = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (steps == 0)
|
||||||
|
return out;
|
||||||
|
const int den = steps + 1;
|
||||||
|
|
||||||
std::vector<int> out(palette.size(), -1);
|
for (int i = 0; i < n; ++i)
|
||||||
for (size_t i = 0; i < palette.size(); ++i) {
|
for (int j = i + 1; j < n; ++j) {
|
||||||
const PaletteEntry &e = palette[i];
|
const Vec3f lab_i = srgb_to_lab(Vec3f(filaments[size_t(i)].r(), filaments[size_t(i)].g(), filaments[size_t(i)].b()));
|
||||||
if (!e.is_mix()) {
|
const Vec3f lab_j = srgb_to_lab(Vec3f(filaments[size_t(j)].r(), filaments[size_t(j)].g(), filaments[size_t(j)].b()));
|
||||||
out[i] = e.a;
|
for (int k = 1; k <= steps; ++k) {
|
||||||
} else if (used[i]) {
|
// k/den of filament i, the rest of j - averaged in Lab, which is what the eye does
|
||||||
const int slot = slot_for_mix ? slot_for_mix(e) : -1;
|
// when the two are interleaved too finely to resolve.
|
||||||
// No room for another slot: the component that dominates the blend is the nearest the print
|
const float t = float(k) / float(den);
|
||||||
// can come.
|
out.push_back({ lab_to_srgb(lab_i * t + lab_j * (1.f - t)), i, j, k, den });
|
||||||
out[i] = slot >= 0 ? slot : (e.num * 2 >= e.den ? e.a : e.b);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
@@ -4850,7 +4434,15 @@ ColorQuantizeFn GLGizmoTextureDisplacement::make_palette_quantizer(const std::ve
|
|||||||
best_pure = int(i);
|
best_pure = int(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A mix only where it clearly beats the nearest single filament, see PREFER_PURE_DE.
|
// A mix is an interleave that only reads as its colour from a distance; up close it is
|
||||||
|
// stripes. So it is spent only where it buys a better match than the nearest single
|
||||||
|
// filament - but "better" was set at ten Delta E, which is not a visible step, it is a
|
||||||
|
// different colour. Measured over the whole cube that threshold turned 94% of the
|
||||||
|
// lookups that wanted a mix back into a pure filament, leaving 38%; along a greyscale
|
||||||
|
// ramp, the shape a height texture actually traces, it cut 80% to 66%. Two Delta E is
|
||||||
|
// about where a side-by-side difference stops being arguable, which is the right place
|
||||||
|
// to start paying for stripes.
|
||||||
|
constexpr float PREFER_PURE_DE = 2.f;
|
||||||
if (best_pure >= 0 && palette[size_t(best)].is_mix() && best_pure_d - best_d < PREFER_PURE_DE)
|
if (best_pure >= 0 && palette[size_t(best)].is_mix() && best_pure_d - best_d < PREFER_PURE_DE)
|
||||||
best = best_pure;
|
best = best_pure;
|
||||||
(*lut)[(size_t(r) * E + size_t(g)) * E + size_t(b)] = uint8_t(best);
|
(*lut)[(size_t(r) * E + size_t(g)) * E + size_t(b)] = uint8_t(best);
|
||||||
@@ -4869,7 +4461,7 @@ ColorQuantizeFn GLGizmoTextureDisplacement::make_palette_quantizer(const std::ve
|
|||||||
TextureDisplacementPrepareResult GLGizmoTextureDisplacement::prepare_mesh(
|
TextureDisplacementPrepareResult GLGizmoTextureDisplacement::prepare_mesh(
|
||||||
const indexed_triangle_set &base, const TextureDisplacementFacetsData &masks,
|
const indexed_triangle_set &base, const TextureDisplacementFacetsData &masks,
|
||||||
const std::vector<TextureDisplacementLayer> &layers, const TextureDisplacementPrepareParams ¶ms,
|
const std::vector<TextureDisplacementLayer> &layers, const TextureDisplacementPrepareParams ¶ms,
|
||||||
const TextureColorSettings &color_settings, const DisplacementProgressFn &progress,
|
const std::vector<PrintableColor> &palette, const DisplacementProgressFn &progress,
|
||||||
BakeStageRecorder *debug)
|
BakeStageRecorder *debug)
|
||||||
{
|
{
|
||||||
TextureDisplacementPrepareResult out;
|
TextureDisplacementPrepareResult out;
|
||||||
@@ -4947,13 +4539,10 @@ TextureDisplacementPrepareResult GLGizmoTextureDisplacement::prepare_mesh(
|
|||||||
// Colour boundaries need triangles of their own - the chord test cannot see them, since
|
// Colour boundaries need triangles of their own - the chord test cannot see them, since
|
||||||
// the height field is perfectly smooth across a change of filament.
|
// the height field is perfectly smooth across a change of filament.
|
||||||
ColorFieldSampler color;
|
ColorFieldSampler color;
|
||||||
// A flat-colour layer is matched against single filaments only, as the bake does, so its
|
if (params.subdiv_color_edge_mm > 0.f && !palette.empty())
|
||||||
// boundaries are refined where the bake will actually change filament.
|
color = make_combined_color_sampler(mesh.its, layers, current, make_palette_quantizer(palette));
|
||||||
if (params.subdiv_color_edge_mm > 0.f && !color_settings.empty())
|
// Note the sampler is built on the *quantizer* alone - the refinement follows perceived
|
||||||
color = make_combined_color_sampler(mesh.its, layers, current, make_palette_quantizer(color_settings.palette),
|
// colour, never the interleaving that realises a mix - the slicer does that per layer.
|
||||||
make_palette_quantizer(color_settings.palette_pure));
|
|
||||||
// The refinement follows perceived colour: a mix is one colour here, however the slicer
|
|
||||||
// interleaves its filaments layer by layer.
|
|
||||||
// "Min edge" is a feature-mode control (it is the floor the curvature test refines down
|
// "Min edge" is a feature-mode control (it is the floor the curvature test refines down
|
||||||
// to); in plain adaptive mode the target edge length is the only criterion, so the floor
|
// to); in plain adaptive mode the target edge length is the only criterion, so the floor
|
||||||
// must not be allowed to silently override a target the user set below it.
|
// must not be allowed to silently override a target the user set below it.
|
||||||
@@ -5241,9 +4830,9 @@ void GLGizmoTextureDisplacement::rebuild_subdivide_preview()
|
|||||||
// Same colour criterion Apply will use, so the previewed wireframe is the mesh that commits.
|
// Same colour criterion Apply will use, so the previewed wireframe is the mesh that commits.
|
||||||
ColorFieldSampler color;
|
ColorFieldSampler color;
|
||||||
if (m_subdivide_color_mm > 0.f && any_layer_colors(*mv)) {
|
if (m_subdivide_color_mm > 0.f && any_layer_colors(*mv)) {
|
||||||
const auto [quantize, quantize_pure] = palette_quantizers();
|
cached_palette(); // refreshes m_palette_quantizer if the filaments changed
|
||||||
color = make_combined_color_sampler(mv->mesh().its, mv->texture_displacement_layers, facets, quantize,
|
color = make_combined_color_sampler(mv->mesh().its, mv->texture_displacement_layers, facets,
|
||||||
quantize_pure);
|
m_palette_quantizer);
|
||||||
}
|
}
|
||||||
its = subdivide_mesh_adaptive(mv->mesh().its, region, m_subdivide_target_mm,
|
its = subdivide_mesh_adaptive(mv->mesh().its, region, m_subdivide_target_mm,
|
||||||
int(mv->mesh().its.indices.size()) + m_subdivide_budget_k * 1000,
|
int(mv->mesh().its.indices.size()) + m_subdivide_budget_k * 1000,
|
||||||
@@ -5755,12 +5344,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
return;
|
return;
|
||||||
ModelVolume *mv = texture_volume();
|
ModelVolume *mv = texture_volume();
|
||||||
|
|
||||||
// The palette also follows project state nobody tells this gizmo about - the filaments and mixed
|
|
||||||
// slots in the sidebar - so it is checked once a frame, and a change re-runs the previews.
|
|
||||||
cached_palette();
|
|
||||||
if (m_palette_changed)
|
|
||||||
m_preview_params_dirty = true;
|
|
||||||
|
|
||||||
float scale = m_parent.get_scale();
|
float scale = m_parent.get_scale();
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
|
int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
|
||||||
@@ -6544,15 +6127,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"of filaments can cover a photo or a gradient. An image of flat colors "
|
"of filaments can cover a photo or a gradient. An image of flat colors "
|
||||||
"prints the same either way. Off uses one filament per area."));
|
"prints the same either way. Off uses one filament per area."));
|
||||||
if (opts.color_mix_enabled) {
|
if (opts.color_mix_enabled) {
|
||||||
cached_palette(); // brings m_palette_filaments up to date
|
|
||||||
// Every mix can become a filament slot, and there are only so many beside the
|
|
||||||
// physical filaments.
|
|
||||||
const int max_mixes = std::max(1, int(EnforcerBlockerType::ExtruderMax) - int(m_palette_filaments.size()));
|
|
||||||
if (int_row("##color_mix_count", _L("Mixed colors"), &opts.color_mix_count, 1, max_mixes, "%d", card_pad))
|
|
||||||
m_preview_params_dirty = true;
|
|
||||||
hover_tip(_u8L("The most mixed filaments a bake may add. They are picked from the "
|
|
||||||
"texture's colors, and only the ones the bake actually uses are created."));
|
|
||||||
// After the slider, so a change shows in the same frame.
|
|
||||||
ImGui::TextDisabled("%s", Slic3r::format(_u8L("%1% printable colors from %2% filaments"),
|
ImGui::TextDisabled("%s", Slic3r::format(_u8L("%1% printable colors from %2% filaments"),
|
||||||
int(cached_palette().size()), int(m_palette_filaments.size())).c_str());
|
int(cached_palette().size()), int(m_palette_filaments.size())).c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,11 @@
|
|||||||
#include "libslic3r/Color.hpp"
|
#include "libslic3r/Color.hpp"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include "libslic3r/TriangleSelector.hpp"
|
#include "libslic3r/TriangleSelector.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
|
||||||
#include "admesh/stl.h"
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Point.hpp"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -62,7 +58,7 @@ public:
|
|||||||
const TextureDisplacementFacetsData &masks,
|
const TextureDisplacementFacetsData &masks,
|
||||||
const std::vector<TextureDisplacementLayer> &layers,
|
const std::vector<TextureDisplacementLayer> &layers,
|
||||||
const TextureDisplacementPrepareParams ¶ms,
|
const TextureDisplacementPrepareParams ¶ms,
|
||||||
const TextureColorSettings &color_settings,
|
const std::vector<PrintableColor> &palette,
|
||||||
const DisplacementProgressFn &progress,
|
const DisplacementProgressFn &progress,
|
||||||
// Optional step capture: receives the mesh
|
// Optional step capture: receives the mesh
|
||||||
// after the remesh and after the refinement,
|
// after the remesh and after the refinement,
|
||||||
@@ -74,43 +70,22 @@ public:
|
|||||||
|
|
||||||
using PaletteEntry = PrintableColor;
|
using PaletteEntry = PrintableColor;
|
||||||
|
|
||||||
// One colour mixes are meant to reach: a bin of the textures' colour histogram, in CIELAB, and how
|
// The printable palette: the loaded filaments (clamped to the sixteen mmu_segmentation_facets can
|
||||||
// much of the image it covers.
|
// address), plus - when `mixing` - every pair of them at evenly spaced ratios.
|
||||||
struct MixTarget
|
|
||||||
{
|
|
||||||
Vec3f lab = Vec3f::Zero();
|
|
||||||
float weight = 0.f;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The colours worth mixing for: those of the colouring layers in `layers` whose image is not made of
|
|
||||||
// flat colours (TextureDetail::flat_colors - those print in single filaments only, so no mix could
|
|
||||||
// serve them). The layers weigh the same and, before only the heaviest bins are kept (which is what
|
|
||||||
// bounds rank_mixes()), together 1. Read from the image as imported, as analyze_texture_detail()
|
|
||||||
// does, so the Smoothing slider does not move the palette around.
|
|
||||||
static std::vector<MixTarget> mix_targets(const std::vector<TextureDisplacementLayer> &layers);
|
|
||||||
|
|
||||||
// Mixes of pairs of `filaments`, best first and at most `limit` of them. Each is the one that most
|
|
||||||
// improves the match to `targets` given those ranked before it, counted only where it beats the
|
|
||||||
// nearest single filament by the quantizer's prefer-pure margin - which is where the quantizer will
|
|
||||||
// actually pick it. Stops early once another mix would make no noticeable difference, so a texture
|
|
||||||
// the filaments already cover gets few mixes or none.
|
|
||||||
//
|
//
|
||||||
// Ratios are the short-cycle ones (k/d for d up to 6): the slicer interleaves a mix layer by layer,
|
// Mixes are averaged in **CIELAB**, not RGB and not subtractively: two filaments interleaved too
|
||||||
// and a long cycle prints as visible bands rather than as a colour. A mix's `rgb` is the colour its
|
// finely to resolve are averaged by the eye, which is what a perceptual space models. Yellow and
|
||||||
// mixed filament slot will show (blend_color_multi(), as the sidebar computes it), so the match, the
|
// blue banded together read as a desaturated grey-green, and that is what the preview must promise
|
||||||
// preview and the slot all agree on what the mix looks like.
|
// - blending them subtractively would show a green the printer cannot produce this way.
|
||||||
static std::vector<PaletteEntry> rank_mixes(const std::vector<ColorRGBA> &filaments,
|
//
|
||||||
const std::vector<MixTarget> &targets, int limit);
|
// How many ratios depends on how many filaments there are, so the palette stays bounded: the
|
||||||
|
// quantizer's lookup cube costs one DeltaE00 per cell per entry to fill, and with sixteen
|
||||||
// The first `count` mixes of `ranking` the project can give a filament slot to. A mix `reusable`
|
// filaments there are already plenty of colours without mixing any of them.
|
||||||
// reports as already having a fixed slot costs nothing; any other uses up one of `free_slots`, and
|
// `max_entries` bounds the whole palette. It is normally the quantizer's own limit, but when the
|
||||||
// is skipped once they run out - so the palette never offers a colour a bake could not print.
|
// mixes become filament slots it has to be the paint mask's instead: a mask can name only
|
||||||
static std::vector<PaletteEntry> pick_mixes(const std::vector<PaletteEntry> &ranking, int count, int free_slots,
|
// EnforcerBlockerType::ExtruderMax states, and every mix now occupies one of them.
|
||||||
const std::function<bool(const PaletteEntry &)> &reusable);
|
static std::vector<PaletteEntry> make_palette(const std::vector<ColorRGBA> &filaments, bool mixing,
|
||||||
|
int max_entries);
|
||||||
// The printable palette: the loaded filaments, entry i being filament i, followed by `mixes`.
|
|
||||||
static std::vector<PaletteEntry> make_palette(const std::vector<ColorRGBA> &filaments,
|
|
||||||
const std::vector<PaletteEntry> &mixes);
|
|
||||||
|
|
||||||
// Maps an image colour to the closest entry of `palette`, perceptually (CIEDE2000 over CIELAB - a
|
// Maps an image colour to the closest entry of `palette`, perceptually (CIEDE2000 over CIELAB - a
|
||||||
// plain RGB distance picks visibly wrong filaments, most obviously between a saturated colour and
|
// plain RGB distance picks visibly wrong filaments, most obviously between a saturated colour and
|
||||||
@@ -122,49 +97,40 @@ public:
|
|||||||
// to a worker thread and outlives the palette it was built from.
|
// to a worker thread and outlives the palette it was built from.
|
||||||
static ColorQuantizeFn make_palette_quantizer(const std::vector<PaletteEntry> &palette);
|
static ColorQuantizeFn make_palette_quantizer(const std::vector<PaletteEntry> &palette);
|
||||||
|
|
||||||
// The filament (0-based) each entry of `palette` prints in, by palette index: a single filament is
|
// Turns a palette index plus a position into the filament to print there, interleaving the two
|
||||||
// itself, a mix is the slot `slot_for_mix` returns for it. Only the mixes `triangle_color` actually
|
// filaments of a mixed entry per `mode`. `layer_height` sizes the Z bands; `cell_mm` the dither
|
||||||
// uses (palette index + 1 per triangle, 0 for none) are asked for, since asking is what creates a
|
|
||||||
// slot. A mix that gets no slot (-1) prints in its dominant component; one nothing uses maps to -1.
|
|
||||||
//
|
|
||||||
// This is what the bake writes into the paint: a palette index is a filament only for the single
|
|
||||||
// filaments, while a mix's slot can sit anywhere among the project's mixed slots.
|
|
||||||
static std::vector<int> palette_filaments(const std::vector<PaletteEntry> &palette,
|
|
||||||
const std::vector<uint8_t> &triangle_color,
|
|
||||||
const std::function<int(const PaletteEntry &)> &slot_for_mix);
|
|
||||||
|
|
||||||
// Everything the jobs need to colour with, for the current volume: the palette, its single-filament
|
// Everything the jobs need to colour with, for the current volume: palette, mix mode, layer
|
||||||
// part, and the despeckle passes. Empty when no layer is actually colouring. Read-only: no filament
|
// height, despeckle. Empty when no layer is actually colouring.
|
||||||
// slot is created here, only when a bake commits (see palette_filaments()).
|
|
||||||
TextureColorSettings color_settings_for(const ModelVolume &mv);
|
TextureColorSettings color_settings_for(const ModelVolume &mv);
|
||||||
|
|
||||||
// The printable palette for the current volume and project, rebuilt only when what it depends on
|
// The printable palette for the current filaments and mixing setting, rebuilt only when either
|
||||||
// changes - see the definition for why that caching is not optional.
|
// actually changes - see the definition for why that caching is not optional.
|
||||||
const std::vector<PaletteEntry> &cached_palette();
|
const std::vector<PaletteEntry> &cached_palette();
|
||||||
// The quantizers for the cached palette and for its single filaments alone (flat-colour images),
|
// Turns every mix in `palette` into a mixed filament slot and rewrites the entry to name that slot
|
||||||
// filled on first use. Only the subdivide preview matches colours on this thread - the jobs build
|
// as a plain filament, so nothing downstream has to know a mix is involved: is_mix() goes false and
|
||||||
// their own from the palette they capture - so a palette rebuild, such as every step of a count
|
// the resolver simply returns it. The per-layer interleaving then happens in the slicer, where it is
|
||||||
// drag, costs no lookup cube unless that preview asks for one.
|
// not limited by how fine the mesh is. Entries whose slot could not be created (the paint-state cap)
|
||||||
std::pair<ColorQuantizeFn, ColorQuantizeFn> palette_quantizers();
|
// fall back to the nearer of the two components.
|
||||||
|
void bind_mixes_to_filament_slots(std::vector<PaletteEntry> &palette);
|
||||||
std::vector<PaletteEntry> m_palette_cache;
|
std::vector<PaletteEntry> m_palette_cache;
|
||||||
ColorQuantizeFn m_palette_quantizer;
|
|
||||||
ColorQuantizeFn m_palette_pure_quantizer;
|
|
||||||
// Set when a rebuild changed the palette the previews were drawn with; cleared by rebuild_preview().
|
|
||||||
bool m_palette_changed = false;
|
|
||||||
// What the cache was built from.
|
|
||||||
std::vector<ColorRGBA> m_palette_filaments;
|
std::vector<ColorRGBA> m_palette_filaments;
|
||||||
std::vector<std::shared_ptr<std::vector<unsigned char>>> m_palette_images; // the colouring layers' images
|
int m_palette_cap = 0; // the max_entries m_palette_cache was built with
|
||||||
std::optional<std::vector<PaletteEntry>> m_mix_ranking; // rank_mixes(), computed on demand
|
|
||||||
bool m_palette_mixing = false;
|
bool m_palette_mixing = false;
|
||||||
int m_palette_mix_count = 0;
|
ColorQuantizeFn m_palette_quantizer;
|
||||||
int m_palette_free_slots = 0;
|
|
||||||
std::vector<std::string> m_palette_slots; // the project's mixed slots
|
|
||||||
|
|
||||||
// The loaded physical filaments, clamped to the states mmu_segmentation_facets can address.
|
// The loaded filaments, clamped to the sixteen mmu_segmentation_facets can address.
|
||||||
static std::vector<ColorRGBA> filament_palette();
|
static std::vector<ColorRGBA> filament_palette();
|
||||||
|
// The Z band height, in mm. One print layer is the ideal, but the interleave is realised per
|
||||||
|
// *facet*: a band thinner than the mesh can resolve does not dither, it beats against the triangle
|
||||||
|
// grid and comes out as broad horizontal stripes - and since MMU segmentation reads facet colour,
|
||||||
|
// it does so in the print too, not only on screen. The refinement edge is chosen from the model's
|
||||||
|
// diagonal and knows nothing about the layer height, so the band is rounded up to a whole number of
|
||||||
|
// layers at least two facet rows tall: still exact on the printer, and representable by the mesh
|
||||||
|
// that has to carry it. Used by both the bake settings and the preview shader, so the two agree.
|
||||||
|
|
||||||
// The Normal preview's triangles, grouped by the palette entry they will print in. Colour is per facet
|
// The Normal preview's triangles, grouped by the filament they will print in. Colour is per facet
|
||||||
// and the palette is small, so the mesh is uploaded once with its index buffer
|
// and there are at most sixteen filaments, so the mesh is uploaded once with its index buffer
|
||||||
// sorted by colour and drawn as one GLModel::render(range) per group - which needs no per-vertex
|
// sorted by colour and drawn as one GLModel::render(range) per group - which needs no per-vertex
|
||||||
// colour attribute, and so no change to GLModel's vertex layouts.
|
// colour attribute, and so no change to GLModel's vertex layouts.
|
||||||
//
|
//
|
||||||
@@ -182,21 +148,6 @@ public:
|
|||||||
// and so whether the colour criterion and the mmu write ever run.
|
// and so whether the colour criterion and the mmu write ever run.
|
||||||
static bool any_layer_colors(const ModelVolume &mv);
|
static bool any_layer_colors(const ModelVolume &mv);
|
||||||
|
|
||||||
// The model's own colour paint (mmu_segmentation_facets) as the gizmo draws it over its surface: the
|
|
||||||
// sub-triangles painted in a filament, grouped by that filament. NONE - the volume's own filament - is
|
|
||||||
// left out, so those triangles keep the gizmo's neutral, as they do in the preview.
|
|
||||||
struct PaintedColors
|
|
||||||
{
|
|
||||||
indexed_triangle_set facets; // over the paint's whole vertex array
|
|
||||||
std::vector<int> source; // per triangle of `facets`: the model triangle it lies in
|
|
||||||
std::vector<int> state; // per triangle of `facets`: its filament state, 1-based
|
|
||||||
|
|
||||||
// The triangles of `facets` outside the model triangles `excluded` marks, in order. A model
|
|
||||||
// triangle past the end of `excluded` is not excluded.
|
|
||||||
std::vector<size_t> outside(const std::vector<bool> &excluded) const;
|
|
||||||
};
|
|
||||||
static PaintedColors painted_colors(const TriangleMesh &mesh, const TriangleSelector::TriangleSplittingData &paint);
|
|
||||||
|
|
||||||
void render_painter_gizmo() override;
|
void render_painter_gizmo() override;
|
||||||
|
|
||||||
// Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/
|
// Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/
|
||||||
@@ -206,9 +157,6 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||||
std::string on_get_name() const override;
|
std::string on_get_name() const override;
|
||||||
// Never in the assemble view: its toolbar does not offer this gizmo, and every preview here is drawn
|
|
||||||
// with the main canvas's instance transform. The base alone would let the keyboard shortcut open it there.
|
|
||||||
bool on_is_activable() const override;
|
|
||||||
|
|
||||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||||
|
|
||||||
@@ -727,9 +675,13 @@ private:
|
|||||||
bool m_shaded_preview_dirty = false;
|
bool m_shaded_preview_dirty = false;
|
||||||
GLModel m_shaded_preview_glmodel;
|
GLModel m_shaded_preview_glmodel;
|
||||||
|
|
||||||
// Translucent tint over the active layer's painted triangles: the paint feedback over a preview (see
|
// Translucent tint over the active layer's painted triangles, drawn on top of whichever preview
|
||||||
// render_painter_gizmo()). Cheap (the painted patch only), translucent so the preview shows through,
|
// is showing. The base painter's own opaque paint highlight (render_triangles()) cannot be used
|
||||||
// and rebuilt live during a stroke.
|
// in either preview mode - it is coincident with the surface and simply covers it - so the only
|
||||||
|
// paint feedback the gizmo had was the relief itself, which meant erasing showed nothing at all
|
||||||
|
// until the stroke ended and the whole preview rebuilt. This is that feedback: cheap (the painted
|
||||||
|
// patch only), translucent (the preview stays visible through it) and rebuilt live during a
|
||||||
|
// stroke.
|
||||||
GLModel m_paint_overlay_glmodel;
|
GLModel m_paint_overlay_glmodel;
|
||||||
// The islands selected in the UV editor, tinted on the model so the pane's selection can be seen
|
// The islands selected in the UV editor, tinted on the model so the pane's selection can be seen
|
||||||
// in place. Rebuilt whenever the pane's selection differs from the one it was built for.
|
// in place. Rebuilt whenever the pane's selection differs from the one it was built for.
|
||||||
@@ -747,23 +699,6 @@ private:
|
|||||||
GLModel m_other_paint_glmodel;
|
GLModel m_other_paint_glmodel;
|
||||||
std::string m_other_paint_key;
|
std::string m_other_paint_key;
|
||||||
void rebuild_other_paint_overlay();
|
void rebuild_other_paint_overlay();
|
||||||
// The model's colour paint, drawn over the surface so the colours a bake wrote stay visible - the
|
|
||||||
// canvas draws no volume while a paint gizmo is open, and the selectors hold only displacement paint.
|
|
||||||
// Left out wherever the preview on screen shows paint of its own (`whole_stack`: every layer's, as the
|
|
||||||
// Normal preview does; otherwise the active layer's), since an opaque overlay there would hide that
|
|
||||||
// preview. Only while a layer colours: it is the colour workflow's result, and every other paint gizmo
|
|
||||||
// shows the model neutral.
|
|
||||||
//
|
|
||||||
// Two levels: the paint's sub-triangles, which take a selector over the whole mesh and change only with
|
|
||||||
// the paint itself, and the part drawn, which follows every flushed stroke.
|
|
||||||
PaintedColors m_painted_colors;
|
|
||||||
std::string m_painted_colors_key;
|
|
||||||
GLModel m_painted_colors_glmodel;
|
|
||||||
std::string m_painted_colors_drawn_key;
|
|
||||||
std::vector<std::pair<int, std::pair<size_t, size_t>>> m_painted_colors_runs; // filament state, index range
|
|
||||||
void rebuild_painted_colors(bool whole_stack);
|
|
||||||
// False when there was nothing to draw.
|
|
||||||
bool render_painted_colors(bool whole_stack);
|
|
||||||
// Whether render_shaded_preview_mesh() would actually draw something. Checked before the real volume
|
// Whether render_shaded_preview_mesh() would actually draw something. Checked before the real volume
|
||||||
// is hidden: with no layer, no texture or no shader the shaded path draws nothing, and hiding the
|
// is hidden: with no layer, no texture or no shader the shaded path draws nothing, and hiding the
|
||||||
// volume for it left the model invisible.
|
// volume for it left the model invisible.
|
||||||
@@ -777,9 +712,11 @@ private:
|
|||||||
int m_shaded_projection_mode = 0;
|
int m_shaded_projection_mode = 0;
|
||||||
Vec3f m_shaded_patch_center = Vec3f::Zero();
|
Vec3f m_shaded_patch_center = Vec3f::Zero();
|
||||||
Vec3f m_shaded_patch_axis = Vec3f::UnitZ();
|
Vec3f m_shaded_patch_axis = Vec3f::UnitZ();
|
||||||
// The palette the fast preview matches each fragment against, captured with its mesh. Empty when the
|
// The palette the fast preview's per-triangle filament indices were built against, captured when
|
||||||
// active layer is not colouring, which is what tells the shader to fall back to the model's own
|
// the mesh was. Empty when the active layer is not colouring, which is what tells the shader to
|
||||||
// colour. Every entry carries the colour it prints in, so drawing it needs nothing else.
|
// fall back to the model's own colour. Held rather than re-read at draw time so the indices baked
|
||||||
|
// into the mesh can never be resolved against a different set of filaments than they were computed
|
||||||
|
// from - loading a filament mid-session would otherwise recolour a stale preview at random.
|
||||||
std::vector<PaletteEntry> m_shaded_preview_palette;
|
std::vector<PaletteEntry> m_shaded_preview_palette;
|
||||||
|
|
||||||
// GPU island drag: while an island is dragged in the UV editor, the displacement mesh is baked once (with
|
// GPU island drag: while an island is dragged in the UV editor, the displacement mesh is baked once (with
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
#include "libslic3r/TextureDisplacement.hpp"
|
#include "libslic3r/TextureDisplacement.hpp"
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -115,17 +114,6 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
|||||||
if (volume == nullptr)
|
if (volume == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// The filament each palette entry prints in. This is where a mix becomes a mixed filament slot,
|
|
||||||
// and only a mix the bake actually painted with: the preview never creates one, so the project
|
|
||||||
// gains only the slots this result needs. Done before anything below names them - adding a slot
|
|
||||||
// runs ModelVolume::update_extruder_count(), which clamps paint above the old filament count.
|
|
||||||
Sidebar &sidebar = plater->sidebar();
|
|
||||||
const std::vector<int> filament_of = GLGizmoTextureDisplacement::palette_filaments(
|
|
||||||
m_input.color.palette, m_triangle_color, [&sidebar](const PrintableColor &mix) {
|
|
||||||
return sidebar.ensure_mixed_filament({ unsigned(mix.a + 1), unsigned(mix.b + 1) },
|
|
||||||
{ mix.a_percent(), 100 - mix.a_percent() });
|
|
||||||
});
|
|
||||||
|
|
||||||
volume->set_mesh(std::move(m_result));
|
volume->set_mesh(std::move(m_result));
|
||||||
volume->set_new_unique_id();
|
volume->set_new_unique_id();
|
||||||
volume->calculate_convex_hull();
|
volume->calculate_convex_hull();
|
||||||
@@ -141,11 +129,9 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
|||||||
const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data();
|
const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data();
|
||||||
if (!existing.bitstream.empty())
|
if (!existing.bitstream.empty())
|
||||||
selector.deserialize(existing, false);
|
selector.deserialize(existing, false);
|
||||||
for (size_t i = 0; i < m_triangle_color.size(); ++i) {
|
for (size_t i = 0; i < m_triangle_color.size(); ++i)
|
||||||
const size_t entry = size_t(m_triangle_color[i]);
|
if (m_triangle_color[i] > 0)
|
||||||
if (entry > 0 && entry <= filament_of.size() && filament_of[entry - 1] >= 0)
|
selector.set_facet(int(i), EnforcerBlockerType(m_triangle_color[i]));
|
||||||
selector.set_facet(int(i), EnforcerBlockerType(filament_of[entry - 1] + 1));
|
|
||||||
}
|
|
||||||
volume->mmu_segmentation_facets.set(selector);
|
volume->mmu_segmentation_facets.set(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,8 @@ private:
|
|||||||
TriangleMesh m_result;
|
TriangleMesh m_result;
|
||||||
// What the bake spent, for the message it leaves behind when the budget capped the detail.
|
// What the bake spent, for the message it leaves behind when the budget capped the detail.
|
||||||
TextureBakeStats m_stats;
|
TextureBakeStats m_stats;
|
||||||
// Per triangle of m_result: the palette entry to print it in, as its index + 1 (0 = leave alone).
|
// Per triangle of m_result: the filament to print it in, as an EnforcerBlockerType value
|
||||||
// Empty unless a layer asked for colour. See TextureColorRequest; finalize() turns these into
|
// (0 = leave alone). Empty unless a layer asked for colour. See TextureColorRequest.
|
||||||
// filaments.
|
|
||||||
std::vector<uint8_t> m_triangle_color;
|
std::vector<uint8_t> m_triangle_color;
|
||||||
std::function<void()> m_on_finished;
|
std::function<void()> m_on_finished;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ void TextureDisplacementDebugJob::process(Ctl &ctl)
|
|||||||
if (m_input.run_prepare && !m_input.options.pipeline_v2) {
|
if (m_input.run_prepare && !m_input.options.pipeline_v2) {
|
||||||
const TextureDisplacementPrepareResult prepared =
|
const TextureDisplacementPrepareResult prepared =
|
||||||
GLGizmoTextureDisplacement::prepare_mesh(mesh, masks, m_input.layers, m_input.prepare_params,
|
GLGizmoTextureDisplacement::prepare_mesh(mesh, masks, m_input.layers, m_input.prepare_params,
|
||||||
m_input.color,
|
m_input.color.palette,
|
||||||
// Preparation is roughly half the run; the bake
|
// Preparation is roughly half the run; the bake
|
||||||
// takes the progress bar from there.
|
// takes the progress bar from there.
|
||||||
[&report](int pct) { return report(1 + pct / 2); },
|
[&report](int pct) { return report(1 + pct / 2); },
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ void TextureDisplacementPrepareJob::process(Ctl &ctl)
|
|||||||
// idle loop.
|
// idle loop.
|
||||||
int last_reported = 1;
|
int last_reported = 1;
|
||||||
m_result = GLGizmoTextureDisplacement::prepare_mesh(m_input.base_mesh, m_input.masks, m_input.layers,
|
m_result = GLGizmoTextureDisplacement::prepare_mesh(m_input.base_mesh, m_input.masks, m_input.layers,
|
||||||
m_input.params, m_input.color,
|
m_input.params, m_input.color.palette,
|
||||||
[&ctl, &status, &last_reported](int percent) {
|
[&ctl, &status, &last_reported](int percent) {
|
||||||
if (ctl.was_canceled())
|
if (ctl.was_canceled())
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ struct TextureDisplacementPreviewInput
|
|||||||
// Mesh coordinates -> world millimetres, so the preview is displaced in the same space the bake
|
// Mesh coordinates -> world millimetres, so the preview is displaced in the same space the bake
|
||||||
// is and the two cannot disagree. See build_texture_displacement().
|
// is and the two cannot disagree. See build_texture_displacement().
|
||||||
Transform3d volume_to_world = Transform3d::Identity();
|
Transform3d volume_to_world = Transform3d::Identity();
|
||||||
// Empty unless a layer is colouring, in which case the preview reports the palette entry per
|
// Empty unless a layer is colouring, in which case the preview reports the filament per triangle
|
||||||
// triangle alongside the mesh, so the Normal view shows the colours the bake will produce.
|
// alongside the mesh, so the Normal view shows what the bake will produce - interleaving included.
|
||||||
TextureColorSettings color;
|
TextureColorSettings color;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A preview result: the displaced mesh, and - when the input carried a palette - one palette entry per
|
// A preview result: the displaced mesh, and - when the input carried a palette - one filament index
|
||||||
// triangle (its index + 1; 0 means "no colour from the texture").
|
// per triangle (an EnforcerBlockerType value; 0 means "no colour from the texture").
|
||||||
struct TextureDisplacementPreviewResult
|
struct TextureDisplacementPreviewResult
|
||||||
{
|
{
|
||||||
indexed_triangle_set mesh;
|
indexed_triangle_set mesh;
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public:
|
|||||||
const std::string& get_vendor() const;
|
const std::string& get_vendor() const;
|
||||||
const std::string& get_renderer() const;
|
const std::string& get_renderer() const;
|
||||||
|
|
||||||
|
// False until a GL context has been made current and queried; the getters below detect
|
||||||
|
// on first use, which needs a current context.
|
||||||
|
bool is_detected() const { return m_detected; }
|
||||||
bool is_core_profile() const { return m_core_profile; }
|
bool is_core_profile() const { return m_core_profile; }
|
||||||
|
|
||||||
bool is_mesa() const;
|
bool is_mesa() const;
|
||||||
|
|||||||
+101
-20
@@ -5048,15 +5048,35 @@ static bool create_mixed_filament_from_result(
|
|||||||
is_mixed_opt->values[new_idx] = true;
|
is_mixed_opt->values[new_idx] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string comp_str;
|
||||||
|
for (size_t i = 0; i < result.components.size(); ++i) {
|
||||||
|
if (i > 0) comp_str += ",";
|
||||||
|
comp_str += std::to_string(result.components[i]);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
auto* comp_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
auto* comp_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||||
while (comp_opt->values.size() <= new_idx) comp_opt->values.push_back(std::string{});
|
while (comp_opt->values.size() <= new_idx) comp_opt->values.push_back(std::string{});
|
||||||
comp_opt->values[new_idx] = format_mixed_components(result.components);
|
comp_opt->values[new_idx] = comp_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ratio_sum = 0;
|
||||||
|
for (int r : result.ratios) ratio_sum += r;
|
||||||
|
if (ratio_sum <= 0) ratio_sum = 100;
|
||||||
|
|
||||||
|
std::string ratio_str;
|
||||||
|
{
|
||||||
|
CNumericLocalesSetter c_locale_setter;
|
||||||
|
for (size_t i = 0; i < result.ratios.size(); ++i) {
|
||||||
|
if (i > 0) ratio_str += ",";
|
||||||
|
char buf[32];
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.4f", (float)result.ratios[i] / ratio_sum);
|
||||||
|
ratio_str += buf;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto* ratios_opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
auto* ratios_opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
||||||
while (ratios_opt->values.size() <= new_idx) ratios_opt->values.push_back(std::string{});
|
while (ratios_opt->values.size() <= new_idx) ratios_opt->values.push_back(std::string{});
|
||||||
ratios_opt->values[new_idx] = format_mixed_ratios(result.ratios);
|
ratios_opt->values[new_idx] = ratio_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project_config.option("filament_mixed_gradient"))
|
if (!project_config.option("filament_mixed_gradient"))
|
||||||
@@ -5116,12 +5136,36 @@ int Sidebar::ensure_mixed_filament(const std::vector<unsigned int> &components,
|
|||||||
return -1;
|
return -1;
|
||||||
if (p->combos_filament.size() < 2)
|
if (p->combos_filament.size() < 2)
|
||||||
return -1;
|
return -1;
|
||||||
if (std::accumulate(ratios.begin(), ratios.end(), 0) <= 0)
|
|
||||||
|
// Normalise the way create_mixed_filament_from_result() stores them, so the comparison below sees
|
||||||
|
// the same text the config holds rather than two spellings of one blend.
|
||||||
|
int ratio_sum = 0;
|
||||||
|
for (const int r : ratios)
|
||||||
|
ratio_sum += r;
|
||||||
|
if (ratio_sum <= 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (const int existing = find_fixed_mixed_filament(wxGetApp().preset_bundle->project_config, components, ratios);
|
std::string comp_str, ratio_str;
|
||||||
existing >= 0)
|
{
|
||||||
return existing;
|
CNumericLocalesSetter c_locale_setter;
|
||||||
|
for (size_t i = 0; i < components.size(); ++i) {
|
||||||
|
if (i > 0) { comp_str += ","; ratio_str += ","; }
|
||||||
|
comp_str += std::to_string(components[i]);
|
||||||
|
char buf[32];
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.4f", float(ratios[i]) / float(ratio_sum));
|
||||||
|
ratio_str += buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &project_config = wxGetApp().preset_bundle->project_config;
|
||||||
|
const auto *is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||||
|
const auto *comp_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||||
|
const auto *ratios_opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
||||||
|
if (is_mixed_opt != nullptr && comp_opt != nullptr && ratios_opt != nullptr)
|
||||||
|
for (size_t i = 0; i < is_mixed_opt->values.size(); ++i)
|
||||||
|
if (is_mixed_opt->values[i] && i < comp_opt->values.size() && i < ratios_opt->values.size() &&
|
||||||
|
comp_opt->values[i] == comp_str && ratios_opt->values[i] == ratio_str)
|
||||||
|
return int(i);
|
||||||
|
|
||||||
if (wxGetApp().preset_bundle->filament_presets.size() >= size_t(EnforcerBlockerType::ExtruderMax))
|
if (wxGetApp().preset_bundle->filament_presets.size() >= size_t(EnforcerBlockerType::ExtruderMax))
|
||||||
return -1;
|
return -1;
|
||||||
@@ -19274,6 +19318,38 @@ void Plater::send_gcode_finish(wxString name)
|
|||||||
auto out_str = GUI::format(_L("The file %s has been sent to the printer's storage space and can be viewed on the printer."), name);
|
auto out_str = GUI::format(_L("The file %s has been sent to the printer's storage space and can be viewed on the printer."), name);
|
||||||
p->notification_manager->push_exporting_finished_notification(out_str, "", false);
|
p->notification_manager->push_exporting_finished_notification(out_str, "", false);
|
||||||
}
|
}
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// export_3mf() assigns archive paths to previously unsaved SVGs. Puts them back, so an export that
|
||||||
|
// is not a project save does not change what a later project save writes.
|
||||||
|
class SvgArchivePathsRestorer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SvgArchivePathsRestorer(Model& model)
|
||||||
|
{
|
||||||
|
for (ModelObject* object : model.objects)
|
||||||
|
for (ModelVolume* volume : object->volumes)
|
||||||
|
if (volume != nullptr && volume->emboss_shape.has_value() && volume->emboss_shape->svg_file.has_value()) {
|
||||||
|
std::string* path_in_3mf = &volume->emboss_shape->svg_file->path_in_3mf;
|
||||||
|
m_paths.emplace_back(path_in_3mf, *path_in_3mf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~SvgArchivePathsRestorer() { restore(); }
|
||||||
|
SvgArchivePathsRestorer(const SvgArchivePathsRestorer&) = delete;
|
||||||
|
SvgArchivePathsRestorer& operator=(const SvgArchivePathsRestorer&) = delete;
|
||||||
|
|
||||||
|
void restore()
|
||||||
|
{
|
||||||
|
for (const auto& [path_in_3mf, previous_path] : m_paths)
|
||||||
|
*path_in_3mf = previous_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::pair<std::string*, std::string>> m_paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void Plater::export_core_3mf()
|
void Plater::export_core_3mf()
|
||||||
{
|
{
|
||||||
wxString path = p->get_export_file(FT_3MF);
|
wxString path = p->get_export_file(FT_3MF);
|
||||||
@@ -19282,6 +19358,23 @@ void Plater::export_core_3mf()
|
|||||||
export_3mf(path_u8, SaveStrategy::Silence);
|
export_3mf(path_u8, SaveStrategy::Silence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Plater::export_3mf_copy(const boost::filesystem::path& output_path)
|
||||||
|
{
|
||||||
|
Model& model = p->model;
|
||||||
|
SvgArchivePathsRestorer svg_paths(model);
|
||||||
|
|
||||||
|
// With no design info, export_3mf() writes the signed-in account's user id as the designer; an
|
||||||
|
// empty one keeps it out. export_3mf() also drops design info without a designer name afterwards.
|
||||||
|
const std::shared_ptr<ModelDesignInfo> design_info = model.design_info;
|
||||||
|
if (design_info == nullptr)
|
||||||
|
model.design_info = std::make_shared<ModelDesignInfo>();
|
||||||
|
ScopeGuard restore_design_info([&model, design_info]() { model.design_info = design_info; });
|
||||||
|
|
||||||
|
// The project save's layout, plus Silence so the project file name stays as it is. Unlike a save
|
||||||
|
// it never adds FullPathSources: a copy is for sharing, and those are paths on this machine.
|
||||||
|
return export_3mf(output_path, SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Export the current project as a "published" 3MF: a pure export that never touches the
|
// Export the current project as a "published" 3MF: a pure export that never touches the
|
||||||
// project's file name, dirty state, backup path or title, and attaches the published metadata
|
// project's file name, dirty state, backup path or title, and attaches the published metadata
|
||||||
// to the model only for the duration of the export (a later Save Project is a normal 3MF).
|
// to the model only for the duration of the export (a later Save Project is a normal 3MF).
|
||||||
@@ -19331,19 +19424,10 @@ int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
|
|||||||
std::string();
|
std::string();
|
||||||
const std::string prev_payload = had_payload ? model.model_info->metadata_items.at(ORCA_PUBLISHED_CONFIG_TAG) : std::string();
|
const std::string prev_payload = had_payload ? model.model_info->metadata_items.at(ORCA_PUBLISHED_CONFIG_TAG) : std::string();
|
||||||
|
|
||||||
// export_3mf() assigns archive paths to previously unsaved SVGs. Preserve those fields too,
|
SvgArchivePathsRestorer svg_paths(model);
|
||||||
// otherwise a publish changes what a later normal project save writes.
|
|
||||||
std::vector<std::pair<std::string*, std::string>> previous_svg_paths;
|
|
||||||
for (ModelObject* object : model.objects)
|
|
||||||
for (ModelVolume* volume : object->volumes)
|
|
||||||
if (volume != nullptr && volume->emboss_shape.has_value() && volume->emboss_shape->svg_file.has_value()) {
|
|
||||||
std::string* path_in_3mf = &volume->emboss_shape->svg_file->path_in_3mf;
|
|
||||||
previous_svg_paths.emplace_back(path_in_3mf, *path_in_3mf);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto restore_temporary_state = [&]() {
|
auto restore_temporary_state = [&]() {
|
||||||
for (const auto& [path_in_3mf, previous_path] : previous_svg_paths)
|
svg_paths.restore();
|
||||||
*path_in_3mf = previous_path;
|
|
||||||
|
|
||||||
if (!had_model_info) {
|
if (!had_model_info) {
|
||||||
model.model_info = nullptr;
|
model.model_info = nullptr;
|
||||||
@@ -21186,9 +21270,6 @@ 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)
|
||||||
|
|||||||
@@ -292,12 +292,11 @@ public:
|
|||||||
// Mixed-color filament sidebar section
|
// Mixed-color filament sidebar section
|
||||||
void add_mixed_filament();
|
void add_mixed_filament();
|
||||||
// The filament slot that blends `components` (1-based physical filament indices) in `ratios`
|
// The filament slot that blends `components` (1-based physical filament indices) in `ratios`
|
||||||
// (percentages) at a fixed ratio, creating it when no existing fixed mixed slot already describes
|
// (percentages), creating it when no existing mixed slot already describes that blend. Returns the
|
||||||
// that blend (see find_fixed_mixed_filament()). Returns the 0-based filament index, or -1 when the
|
// 0-based filament index, or -1 when the paint-state cap leaves no room for another one.
|
||||||
// paint-state cap leaves no room for another one.
|
|
||||||
//
|
//
|
||||||
// Exists so a feature that needs a blend can ask for one without going through the modal dialog:
|
// Exists so a feature that needs a blend can ask for one without going through the modal dialog:
|
||||||
// a texture displacement bake turns each mix it painted with into a slot, which is what moves the
|
// the texture displacement gizmo turns each mix in its palette into a slot, which is what moves the
|
||||||
// interleaving from its own paint mask to the slicer, where it happens per layer.
|
// interleaving from its own paint mask to the slicer, where it happens per layer.
|
||||||
int ensure_mixed_filament(const std::vector<unsigned int> &components, const std::vector<int> &ratios);
|
int ensure_mixed_filament(const std::vector<unsigned int> &components, const std::vector<int> &ratios);
|
||||||
void edit_mixed_filament(size_t idx);
|
void edit_mixed_filament(size_t idx);
|
||||||
@@ -566,6 +565,9 @@ public:
|
|||||||
void export_gcode_3mf(bool export_all = false);
|
void export_gcode_3mf(bool export_all = false);
|
||||||
void send_gcode_finish(wxString name);
|
void send_gcode_finish(wxString name);
|
||||||
void export_core_3mf();
|
void export_core_3mf();
|
||||||
|
// Write the project to output_path as a copy: the project's file name, dirty state and model stay
|
||||||
|
// as they are, and the signed-in account is not added as the designer. False when the write fails.
|
||||||
|
bool export_3mf_copy(const boost::filesystem::path& output_path);
|
||||||
// Export a "published" 3MF embedding the author-selected settings in the file metadata; a
|
// Export a "published" 3MF embedding the author-selected settings in the file metadata; a
|
||||||
// pure export that leaves the in-memory project untouched.
|
// pure export that leaves the in-memory project untouched.
|
||||||
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
|
||||||
|
|||||||
@@ -1,16 +1,36 @@
|
|||||||
#include "PluginHostBindings.hpp"
|
#include "PluginHostBindings.hpp"
|
||||||
|
#include <pybind11/cast.h>
|
||||||
#include <pybind11/pybind11.h>
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/pytypes.h>
|
||||||
|
|
||||||
#include <libslic3r/Model.hpp>
|
#include <libslic3r/Model.hpp>
|
||||||
#include <libslic3r/PresetBundle.hpp>
|
#include <libslic3r/PresetBundle.hpp>
|
||||||
|
#include <libslic3r/AppConfig.hpp>
|
||||||
|
#include <libslic3r/Semver.hpp>
|
||||||
|
#include <libslic3r_version.h>
|
||||||
|
#include <slic3r/GUI/BuildCommit.hpp>
|
||||||
|
#include <slic3r/GUI/DeviceCore/DevManager.h>
|
||||||
|
#include <slic3r/GUI/DeviceManager.hpp>
|
||||||
#include <slic3r/GUI/GUI.hpp>
|
#include <slic3r/GUI/GUI.hpp>
|
||||||
#include <slic3r/GUI/GUI_App.hpp>
|
#include <slic3r/GUI/GUI_App.hpp>
|
||||||
|
#include <slic3r/GUI/OpenGLManager.hpp>
|
||||||
#include <slic3r/GUI/Plater.hpp>
|
#include <slic3r/GUI/Plater.hpp>
|
||||||
|
#include <slic3r/Utils/CloudProvider.hpp>
|
||||||
|
#include <slic3r/Utils/NetworkAgent.hpp>
|
||||||
|
#include <slic3r/Utils/NetworkAgentFactory.hpp>
|
||||||
|
#include <slic3r/Utils/bambu_networking.hpp>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/optional/optional.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <wx/app.h>
|
#include <wx/app.h>
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <wx/thread.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
|
|
||||||
@@ -41,6 +61,94 @@ PresetBundle* current_preset_bundle()
|
|||||||
return preset_bundle;
|
return preset_bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GUI::GUI_App& current_app()
|
||||||
|
{
|
||||||
|
if (wxTheApp == nullptr)
|
||||||
|
throw std::runtime_error("OrcaSlicer application is not initialized");
|
||||||
|
return GUI::wxGetApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The plater and the device list are only safe to touch from the UI thread, and the 3MF export
|
||||||
|
// renders thumbnails, which needs the UI thread's GL context.
|
||||||
|
void require_main_thread(const char* function_name)
|
||||||
|
{
|
||||||
|
if (!wxIsMainThread())
|
||||||
|
throw std::runtime_error(std::string(function_name) + "() must be called from the UI thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_3mf_copy(GUI::Plater& plater, const std::string& path)
|
||||||
|
{
|
||||||
|
require_main_thread("export_3mf_copy");
|
||||||
|
if (!boost::iends_with(path, ".3mf"))
|
||||||
|
throw py::value_error("export_3mf_copy() needs a path ending in .3mf");
|
||||||
|
// The thumbnails are rendered by the 3D view, which may not exist yet while plugins load.
|
||||||
|
if (!GUI::OpenGLManager::get_gl_info().is_detected())
|
||||||
|
throw std::runtime_error("export_3mf_copy() needs the 3D view, which is not ready yet");
|
||||||
|
// The file is written from C++, so raise the audit event Python's open(path, "w") raises: the
|
||||||
|
// plugin gets the same deny list, permissions and prompt as for writing the file itself.
|
||||||
|
if (PySys_Audit("open", "ssi", path.c_str(), "w", 0) < 0)
|
||||||
|
throw py::error_already_set();
|
||||||
|
if (!plater.export_3mf_copy(GUI::into_path(GUI::from_u8(path))))
|
||||||
|
throw std::runtime_error("Failed to write " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
py::object gl_info()
|
||||||
|
{
|
||||||
|
const GUI::OpenGLManager::GLInfo& info = GUI::OpenGLManager::get_gl_info();
|
||||||
|
if (!info.is_detected())
|
||||||
|
return py::none();
|
||||||
|
py::dict out;
|
||||||
|
out["vendor"] = info.get_vendor();
|
||||||
|
out["renderer"] = info.get_renderer();
|
||||||
|
out["version"] = info.get_version();
|
||||||
|
out["glsl_version"] = info.get_glsl_version();
|
||||||
|
out["core_profile"] = info.is_core_profile();
|
||||||
|
return std::move(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
py::dict app_info()
|
||||||
|
{
|
||||||
|
GUI::GUI_App& app = current_app();
|
||||||
|
// The app config has no lock of its own; the UI thread is where it is written.
|
||||||
|
require_main_thread("app_info");
|
||||||
|
py::dict out;
|
||||||
|
out["version"] = SoftFever_VERSION;
|
||||||
|
out["build"] = GUI::build_commit_label;
|
||||||
|
out["mode"] = app.is_editor() ? "editor" : "gcode viewer";
|
||||||
|
out["language"] = GUI::into_u8(app.current_language_code_safe());
|
||||||
|
// The app config file is deny-listed for plugins; these are the parts a bug report needs.
|
||||||
|
const boost::optional<Semver>& config_version = app.last_config_version();
|
||||||
|
out["app_config_version"] = config_version && config_version->valid() ? config_version->to_string_sf() : std::string();
|
||||||
|
out["stealth_mode"] = app.app_config != nullptr && app.app_config->get_stealth_mode();
|
||||||
|
out["is_signed_in"] = app.is_user_login(ORCA_CLOUD_PROVIDER);
|
||||||
|
out["is_bambu_signed_in"] = app.is_user_login(BBL_CLOUD_PROVIDER);
|
||||||
|
out["network_plugin_version"] = NetworkAgent::is_network_module_loaded() ? NetworkAgent::get_version() : std::string();
|
||||||
|
const NetworkLibraryLoadError load_error = NetworkAgent::get_load_error();
|
||||||
|
out["network_plugin_error"] = load_error.has_error ? load_error.message : std::string();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
py::object selected_printer()
|
||||||
|
{
|
||||||
|
GUI::GUI_App& app = current_app();
|
||||||
|
require_main_thread("selected_printer");
|
||||||
|
DeviceManager* devices = app.getDeviceManager();
|
||||||
|
MachineObject* machine = devices != nullptr ? devices->get_selected_machine() : nullptr;
|
||||||
|
if (machine == nullptr)
|
||||||
|
return py::none();
|
||||||
|
// Agent, model, link and firmware only: no name, serial, address or access code. Other agents
|
||||||
|
// fill the model and firmware with placeholders, so those come from Bambu printers only.
|
||||||
|
const bool bambu = machine->printer_agent_id == BBL_PRINTER_AGENT_ID;
|
||||||
|
py::dict out;
|
||||||
|
out["agent"] = machine->printer_agent_id;
|
||||||
|
out["model_id"] = bambu ? machine->printer_type : std::string();
|
||||||
|
out["connection"] = machine->connection_type();
|
||||||
|
out["online"] = machine->is_online();
|
||||||
|
out["connected"] = machine->is_connected();
|
||||||
|
out["firmware"] = bambu ? machine->get_ota_version() : std::string();
|
||||||
|
return std::move(out);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// Access to the live GUI application: the Plater and the module-level
|
// Access to the live GUI application: the Plater and the module-level
|
||||||
@@ -52,7 +160,15 @@ void host_bindings::register_app(py::module_& host)
|
|||||||
.def("model", static_cast<Model& (GUI::Plater::*)()>(&GUI::Plater::model), py::return_value_policy::reference_internal)
|
.def("model", static_cast<Model& (GUI::Plater::*)()>(&GUI::Plater::model), py::return_value_policy::reference_internal)
|
||||||
.def("is_project_dirty", &GUI::Plater::is_project_dirty)
|
.def("is_project_dirty", &GUI::Plater::is_project_dirty)
|
||||||
.def("is_presets_dirty", &GUI::Plater::is_presets_dirty)
|
.def("is_presets_dirty", &GUI::Plater::is_presets_dirty)
|
||||||
.def("inside_snapshot_capture", &GUI::Plater::inside_snapshot_capture);
|
.def("inside_snapshot_capture", &GUI::Plater::inside_snapshot_capture)
|
||||||
|
// Path the project was last opened from or saved to; empty while it has never been saved.
|
||||||
|
.def("project_path", [](GUI::Plater& plater) {
|
||||||
|
require_main_thread("project_path");
|
||||||
|
return GUI::into_u8(plater.get_project_filename(".3mf"));
|
||||||
|
})
|
||||||
|
.def("export_3mf_copy", &export_3mf_copy, py::arg("path"),
|
||||||
|
"Write the current project, unsaved changes included, to path as a 3MF copy. The project's "
|
||||||
|
"file name and saved state are not changed, and the signed-in account is not written as the designer.");
|
||||||
|
|
||||||
host.def("plater", ¤t_plater, py::return_value_policy::reference);
|
host.def("plater", ¤t_plater, py::return_value_policy::reference);
|
||||||
host.def("model", []() -> Model& {
|
host.def("model", []() -> Model& {
|
||||||
@@ -68,6 +184,16 @@ void host_bindings::register_app(py::module_& host)
|
|||||||
throw std::runtime_error("OrcaSlicer application is not initialized");
|
throw std::runtime_error("OrcaSlicer application is not initialized");
|
||||||
return GUI::into_u8(GUI::wxGetApp().current_language_code_safe());
|
return GUI::into_u8(GUI::wxGetApp().current_language_code_safe());
|
||||||
});
|
});
|
||||||
|
host.def("app_info", &app_info,
|
||||||
|
"The running app: version, build, mode, language, app_config_version (the version that wrote the app config "
|
||||||
|
"before this run, empty when there was none), stealth_mode, is_signed_in (Orca Cloud), is_bambu_signed_in (Bambu Cloud), network_plugin_version (empty "
|
||||||
|
"when the Bambu network plugin is not loaded) and network_plugin_error (empty unless it failed to load).");
|
||||||
|
host.def("gl_info", &gl_info,
|
||||||
|
"OpenGL vendor, renderer, version, glsl_version and core_profile of the 3D view; None before it has been created.");
|
||||||
|
host.def("selected_printer", &selected_printer,
|
||||||
|
"Agent, model_id, connection ('lan' or 'cloud'), online, connected and firmware of the printer selected in the "
|
||||||
|
"device list; model_id and firmware are empty for non-Bambu agents. None when none is selected. Print hosts "
|
||||||
|
"configured in the printer preset (OctoPrint and the like) are not in the device list.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
#include <catch2/matchers/catch_matchers.hpp>
|
#include <catch2/matchers/catch_matchers.hpp>
|
||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/BoundingBox.hpp"
|
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
@@ -20,7 +18,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <initializer_list>
|
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Point.hpp"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -102,22 +99,6 @@ TEST_CASE("Reserved keyword detection reports every offending line", "[GCodeProc
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void process_gcode(const std::string &gcode, GCodeProcessorResult &result)
|
|
||||||
{
|
|
||||||
FullPrintConfig config;
|
|
||||||
config.gcode_flavor.value = gcfMarlinFirmware;
|
|
||||||
// s_IsBBLPrinter selects the "; FEATURE: " role tags the G-code uses.
|
|
||||||
const bool was_bbl_printer = GCodeProcessor::s_IsBBLPrinter;
|
|
||||||
const ScopeGuard restore_bbl_printer([was_bbl_printer] { GCodeProcessor::s_IsBBLPrinter = was_bbl_printer; });
|
|
||||||
GCodeProcessor::s_IsBBLPrinter = true;
|
|
||||||
ScopedTemporaryFile temp(".gcode");
|
|
||||||
std::ofstream(temp.string()) << gcode;
|
|
||||||
GCodeProcessor processor;
|
|
||||||
processor.apply_config(config);
|
|
||||||
processor.process_file(temp.string());
|
|
||||||
result = std::move(processor.extract_result());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closed outer-wall squares, each after a fast travel and before an inner-wall move, so the processor
|
// Closed outer-wall squares, each after a fast travel and before an inner-wall move, so the processor
|
||||||
// records seams and inserts actual speed moves. virtual_moves adds a VG1 move after each square.
|
// records seams and inserts actual speed moves. virtual_moves adds a VG1 move after each square.
|
||||||
void process_squares(int squares, GCodeProcessorResult &result, bool virtual_moves = false)
|
void process_squares(int squares, GCodeProcessorResult &result, bool virtual_moves = false)
|
||||||
@@ -133,42 +114,18 @@ void process_squares(int squares, GCodeProcessorResult &result, bool virtual_mov
|
|||||||
if (virtual_moves)
|
if (virtual_moves)
|
||||||
gcode << "VG1 X20 Y30 F12000\n";
|
gcode << "VG1 X20 Y30 F12000\n";
|
||||||
}
|
}
|
||||||
process_gcode(gcode.str(), result);
|
FullPrintConfig config;
|
||||||
}
|
config.gcode_flavor.value = gcfMarlinFirmware;
|
||||||
|
// s_IsBBLPrinter selects the "; FEATURE: " role tags this G-code uses.
|
||||||
// Objects A and B on the first layer and A again on the second, with A's brim and support. The skirt and the
|
const bool was_bbl_printer = GCodeProcessor::s_IsBBLPrinter;
|
||||||
// prime tower belong to neither.
|
const ScopeGuard restore_bbl_printer([was_bbl_printer] { GCodeProcessor::s_IsBBLPrinter = was_bbl_printer; });
|
||||||
void process_two_objects(GCodeProcessorResult &result)
|
GCodeProcessor::s_IsBBLPrinter = true;
|
||||||
{
|
ScopedTemporaryFile temp(".gcode");
|
||||||
std::ostringstream gcode;
|
std::ofstream(temp.string()) << gcode.str();
|
||||||
gcode << "M83\nG90\n"
|
GCodeProcessor processor;
|
||||||
<< "; CHANGE_LAYER\n; LAYER_HEIGHT: 0.2\nG1 Z0.2 F12000\n"
|
processor.apply_config(config);
|
||||||
<< "; FEATURE: Skirt\nG1 X0 Y100 E5 F3000\n"
|
processor.process_file(temp.string());
|
||||||
<< "; FEATURE: Brim\nG1 X8 Y8 F12000\nG1 X12 Y8 E1 F3000\n"
|
result = std::move(processor.extract_result());
|
||||||
<< "; FEATURE: Support\nG1 X10 Y20 F12000\nG1 X10 Y30 E1 F3000\n"
|
|
||||||
<< "; FEATURE: Outer wall\nG1 X10 Y10 F12000\nG1 X20 Y10 E1 F3000\n"
|
|
||||||
<< "; FEATURE: Outer wall\nG1 X50 Y50 F12000\nG1 X60 Y50 E2 F3000\n"
|
|
||||||
<< "; FEATURE: Prime tower\nG1 X80 Y80 F12000\nG1 X90 Y80 E1 F3000\n"
|
|
||||||
<< "; CHANGE_LAYER\n; LAYER_HEIGHT: 0.2\nG1 Z0.4 F12000\n"
|
|
||||||
<< "; FEATURE: Outer wall\nG1 X10 Y10 F12000\nG1 X20 Y10 E1 F3000\n";
|
|
||||||
process_gcode(gcode.str(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bead centers of process_two_objects(), half the 0.2 mm layer below the nozzle.
|
|
||||||
const Vec3d a_brim(10., 8., 0.1), a_support(10., 25., 0.1), a_wall_0(15., 10., 0.1), a_wall_1(15., 10., 0.3), b_wall(55., 50., 0.1);
|
|
||||||
|
|
||||||
Vec3d center_of(const GCodeProcessorResult::ObjectMass::Sum &sum) { return sum.moment / sum.mass; }
|
|
||||||
|
|
||||||
// One filament, so each bead weighs as much as the E it was extruded with.
|
|
||||||
Vec3d weighted_center(std::initializer_list<std::pair<double, Vec3d>> beads)
|
|
||||||
{
|
|
||||||
double mass = 0.;
|
|
||||||
Vec3d moment = Vec3d::Zero();
|
|
||||||
for (const auto &[e, center] : beads) {
|
|
||||||
mass += e;
|
|
||||||
moment += e * center;
|
|
||||||
}
|
|
||||||
return moment / mass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_block_move(const GCodeProcessorResult::MoveVertex &move)
|
bool is_block_move(const GCodeProcessorResult::MoveVertex &move)
|
||||||
@@ -324,181 +281,3 @@ TEST_CASE("Rewritten G-code that cannot be re-read keeps the moves and hides the
|
|||||||
REQUIRE(result.moves.size() == exported_moves.size());
|
REQUIRE(result.moves.size() == exported_moves.size());
|
||||||
CHECK(result.moves.back().gcode_id == exported_moves.back().gcode_id);
|
CHECK(result.moves.back().gcode_id == exported_moves.back().gcode_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("The plate's center of mass takes every extrusion of G-code without a print behind it", "[GCodeProcessor]")
|
|
||||||
{
|
|
||||||
GCodeProcessorResult result;
|
|
||||||
process_two_objects(result);
|
|
||||||
|
|
||||||
CHECK(result.object_masses.empty());
|
|
||||||
CHECK(result.body_masses.empty());
|
|
||||||
const GCodeProcessorResult::ObjectMass &plate = result.plate_mass;
|
|
||||||
REQUIRE(plate.printed_up_to_layer.size() == 2);
|
|
||||||
CHECK_THAT((center_of(plate.printed_up_to_layer.front()) -
|
|
||||||
weighted_center({ { 1., a_brim }, { 1., a_support }, { 1., a_wall_0 }, { 2., b_wall } })).norm(),
|
|
||||||
Catch::Matchers::WithinAbs(0., 1e-5));
|
|
||||||
CHECK_THAT((center_of(plate.printed_up_to_layer.back()) -
|
|
||||||
weighted_center({ { 1., a_brim }, { 1., a_support }, { 1., a_wall_0 }, { 2., b_wall }, { 1., a_wall_1 } })).norm(),
|
|
||||||
Catch::Matchers::WithinAbs(0., 1e-5));
|
|
||||||
|
|
||||||
// Each bead weighs its volume at the default density and spreads along its move, (a^2 + ab + b^2) / 3 for one from
|
|
||||||
// a to b: the brim from x 8 to 12 at y 8, the support at x 10 from y 20 to 30, A's walls from x 10 to 20 at y 10 and
|
|
||||||
// B's from x 50 to 60 at y 50 with twice the filament, all at z 0.1 but A's second wall at 0.3.
|
|
||||||
const GCodeProcessorResult::ObjectMass::Sum total = plate.total();
|
|
||||||
CHECK_THAT(total.mass / total.volume, Catch::Matchers::WithinRel(double(DEFAULT_FILAMENT_DENSITY), 1e-6));
|
|
||||||
const Vec3d second = total.second / total.mass;
|
|
||||||
CHECK_THAT(second.x(), Catch::Matchers::WithinRel((304. / 3. + 100. + 2. * 700. / 3. + 2. * 9100. / 3.) / 6., 1e-6));
|
|
||||||
CHECK_THAT(second.y(), Catch::Matchers::WithinRel((64. + 1900. / 3. + 2. * 100. + 2. * 2500.) / 6., 1e-6));
|
|
||||||
CHECK_THAT(second.z(), Catch::Matchers::WithinRel((5. * 0.01 + 0.09) / 6., 1e-5));
|
|
||||||
// The beads' center lines, brim and support included, from the first layer's bottom to the second's top.
|
|
||||||
CHECK_THAT((plate.box.min - Vec3d(8., 8., 0.)).norm(), Catch::Matchers::WithinAbs(0., 1e-5));
|
|
||||||
CHECK_THAT((plate.box.max - Vec3d(60., 50., 0.4)).norm(), Catch::Matchers::WithinAbs(0., 1e-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Each sliced cube's center of mass is its center, and the brim lowers the plate's printed one", "[GCodeProcessor]")
|
|
||||||
{
|
|
||||||
const bool copies = GENERATE(false, true);
|
|
||||||
INFO((copies ? "two copies of one cube" : "two cubes"));
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "outer_only" }, { "brim_width", 5 }, { "combine_brims", 0 } });
|
|
||||||
std::vector<TriangleMesh> cubes{ Test::cube(20) };
|
|
||||||
if (!copies)
|
|
||||||
cubes.emplace_back(Test::cube(20));
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
Test::init_print(std::move(cubes), print, model, config, nullptr, true, copies ? 2 : 1);
|
|
||||||
GCodeProcessorResult result;
|
|
||||||
Test::gcode(print, &result);
|
|
||||||
|
|
||||||
CHECK(result.body_masses.empty());
|
|
||||||
REQUIRE(result.object_masses.size() == 2);
|
|
||||||
for (const ModelObject *object : model.objects)
|
|
||||||
for (size_t instance = 0; instance < object->instances.size(); ++instance) {
|
|
||||||
const Vec3d center = object->instance_bounding_box(instance).center();
|
|
||||||
const auto mass = std::min_element(result.object_masses.begin(), result.object_masses.end(), [¢er](const auto &l, const auto &r) {
|
|
||||||
return (center_of(l.total()) - center).squaredNorm() < (center_of(r.total()) - center).squaredNorm();
|
|
||||||
});
|
|
||||||
// Off the center only by the infill's alignment and the top and bottom shells.
|
|
||||||
const Vec3d part = center_of(mass->total());
|
|
||||||
CHECK_THAT(part.x(), Catch::Matchers::WithinAbs(center.x(), 0.5));
|
|
||||||
CHECK_THAT(part.y(), Catch::Matchers::WithinAbs(center.y(), 0.5));
|
|
||||||
CHECK_THAT(part.z(), Catch::Matchers::WithinAbs(center.z(), 1.));
|
|
||||||
// The outer walls' center lines run half a line inside the cube's sides, of copies touching each other too.
|
|
||||||
const BoundingBoxf3 box = object->instance_bounding_box(instance);
|
|
||||||
for (int axis = 0; axis < 3; ++axis) {
|
|
||||||
CHECK_THAT(mass->box.min[axis], Catch::Matchers::WithinAbs(box.min[axis], 0.3));
|
|
||||||
CHECK_THAT(mass->box.max[axis], Catch::Matchers::WithinAbs(box.max[axis], 0.3));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GCodeProcessorResult::ObjectMass::Sum objects;
|
|
||||||
for (const GCodeProcessorResult::ObjectMass &object : result.object_masses)
|
|
||||||
objects.add(object.total());
|
|
||||||
const GCodeProcessorResult::ObjectMass::Sum plate = result.plate_mass.total();
|
|
||||||
CHECK(plate.mass > objects.mass);
|
|
||||||
CHECK(center_of(plate).z() < center_of(objects).z());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Each cube's raft is its support, centered below it", "[GCodeProcessor]")
|
|
||||||
{
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "raft_layers", 3 } });
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
Test::init_print({ Test::cube(20), Test::cube(20) }, print, model, config);
|
|
||||||
GCodeProcessorResult result;
|
|
||||||
Test::gcode(print, &result);
|
|
||||||
|
|
||||||
REQUIRE(result.support_masses.size() == 2);
|
|
||||||
for (size_t i = 0; i < 2; ++i) {
|
|
||||||
const GCodeProcessorResult::ObjectMass::Sum support = result.support_masses[i].total();
|
|
||||||
const Vec3d object = center_of(result.object_masses[i].total());
|
|
||||||
REQUIRE(support.mass > 0.);
|
|
||||||
CHECK_THAT(center_of(support).x(), Catch::Matchers::WithinAbs(object.x(), 1.));
|
|
||||||
CHECK_THAT(center_of(support).y(), Catch::Matchers::WithinAbs(object.y(), 1.));
|
|
||||||
CHECK(center_of(support).z() < 1.);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A spiral vase cube counts all its extrusions, rising through each layer", "[GCodeProcessor]")
|
|
||||||
{
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "spiral_mode", 1 }, { "wall_loops", 1 },
|
|
||||||
{ "top_shell_layers", 0 }, { "sparse_infill_density", 0 } });
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
Test::init_print({ Test::cube(20) }, print, model, config);
|
|
||||||
GCodeProcessorResult result;
|
|
||||||
Test::gcode(print, &result);
|
|
||||||
|
|
||||||
REQUIRE(result.object_masses.size() == 1);
|
|
||||||
CHECK_THAT(result.object_masses.front().total().mass, Catch::Matchers::WithinRel(result.plate_mass.total().mass, 1e-6));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Each separate part of an assembly gets its center of mass, overlapping parts one", "[GCodeProcessor]")
|
|
||||||
{
|
|
||||||
const bool overlapping = GENERATE(false, true);
|
|
||||||
// Separated infills finds the bodies first, which the G-code export then takes.
|
|
||||||
const bool separated = GENERATE(false, true);
|
|
||||||
INFO((overlapping ? "overlapping parts" : "separate parts") << (separated ? ", separated infills" : ""));
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "separated_infills", separated ? 1 : 0 } });
|
|
||||||
TriangleMesh first = make_cube(20, 20, 20);
|
|
||||||
TriangleMesh second = make_cube(20, 20, 20);
|
|
||||||
first.translate(50, 50, 0);
|
|
||||||
second.translate(overlapping ? 60 : 90, 50, 0);
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
Test::init_print({ first }, print, model, config, nullptr, false);
|
|
||||||
model.objects.front()->add_volume(std::move(second), ModelVolumeType::MODEL_PART, false);
|
|
||||||
print.apply(model, config);
|
|
||||||
GCodeProcessorResult result;
|
|
||||||
Test::gcode(print, &result);
|
|
||||||
|
|
||||||
REQUIRE(result.object_masses.size() == 1);
|
|
||||||
CHECK(result.object_masses.front().assembly);
|
|
||||||
// One body is the object itself.
|
|
||||||
if (overlapping) {
|
|
||||||
CHECK(result.body_masses.empty());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
REQUIRE(result.body_masses.size() == 2);
|
|
||||||
CHECK(print.objects().front()->separated_body_bboxes().size() == (separated ? 2 : 0));
|
|
||||||
const ModelObject &object = *model.objects.front();
|
|
||||||
for (const ModelVolume *volume : object.volumes) {
|
|
||||||
const Vec3d center = volume->mesh().transformed_bounding_box(object.instances.front()->get_matrix() * volume->get_matrix()).center();
|
|
||||||
const auto body = std::min_element(result.body_masses.begin(), result.body_masses.end(), [¢er](const auto &l, const auto &r) {
|
|
||||||
return (center_of(l.total()) - center).squaredNorm() < (center_of(r.total()) - center).squaredNorm();
|
|
||||||
});
|
|
||||||
const Vec3d part = center_of(body->total());
|
|
||||||
CHECK_THAT(part.x(), Catch::Matchers::WithinAbs(center.x(), 0.5));
|
|
||||||
CHECK_THAT(part.y(), Catch::Matchers::WithinAbs(center.y(), 0.5));
|
|
||||||
CHECK_THAT(part.z(), Catch::Matchers::WithinAbs(center.z(), 1.));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Each extrusion weighs its filament's density", "[GCodeProcessor]")
|
|
||||||
{
|
|
||||||
// Two like cubes, the second's filament three times as dense.
|
|
||||||
DynamicPrintConfig config = Test::multifilament_config(2, { { "filament_density", "1,3" }, { "skirt_loops", 0 }, { "brim_type", "no_brim" } });
|
|
||||||
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides{ { { "extruder", 1 } }, { { "extruder", 2 } } };
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
Test::init_print({ Test::cube(20), Test::cube(20) }, print, model, config, &overrides);
|
|
||||||
GCodeProcessorResult result;
|
|
||||||
Test::gcode(print, &result);
|
|
||||||
|
|
||||||
REQUIRE(result.object_masses.size() == 2);
|
|
||||||
std::vector<const GCodeProcessorResult::ObjectMass *> masses;
|
|
||||||
for (const ModelObject *object : model.objects) {
|
|
||||||
const Vec3d center = object->instance_bounding_box(0).center();
|
|
||||||
masses.emplace_back(&*std::min_element(result.object_masses.begin(), result.object_masses.end(), [¢er](const auto &l, const auto &r) {
|
|
||||||
return (center_of(l.total()) - center).squaredNorm() < (center_of(r.total()) - center).squaredNorm();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
CHECK_THAT(masses[1]->total().mass / masses[0]->total().mass, Catch::Matchers::WithinRel(3., 0.02));
|
|
||||||
// The plate's center lies three quarters of the way to the dense cube.
|
|
||||||
const Vec3d plate = center_of(result.plate_mass.total());
|
|
||||||
const Vec3d light = center_of(masses[0]->total());
|
|
||||||
const Vec3d dense = center_of(masses[1]->total());
|
|
||||||
CHECK_THAT((plate - light).dot(dense - light) / (dense - light).squaredNorm(), Catch::Matchers::WithinAbs(0.75, 0.01));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_ordering_strategies.cpp
|
test_ordering_strategies.cpp
|
||||||
# test_png_io.cpp
|
# test_png_io.cpp
|
||||||
test_indexed_triangle_set.cpp
|
test_indexed_triangle_set.cpp
|
||||||
test_connected_bodies.cpp
|
|
||||||
test_texture_displacement.cpp
|
test_texture_displacement.cpp
|
||||||
test_instance_lock.cpp
|
test_instance_lock.cpp
|
||||||
../libnest2d/printer_parts.cpp
|
../libnest2d/printer_parts.cpp
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include <catch2/matchers/catch_matchers.hpp>
|
|
||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
||||||
|
|
||||||
#include "libslic3r/BoundingBox.hpp"
|
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
|
||||||
#include "libslic3r/Geometry.hpp"
|
|
||||||
#include "libslic3r/Point.hpp"
|
|
||||||
#include "libslic3r/Polygon.hpp"
|
|
||||||
#include "libslic3r/ConnectedBodies.hpp"
|
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
|
||||||
#include "libslic3r/libslic3r.h"
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
using namespace Slic3r;
|
|
||||||
using Catch::Matchers::WithinAbs;
|
|
||||||
using Catch::Matchers::WithinRel;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
ExPolygon rectangle(double x, double width) { return ExPolygon(Polygon::new_scale({ { x, 0. }, { x + width, 0. }, { x + width, 10. }, { x, 10. } })); }
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("Islands overlapping their neighbors' make one body", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const ExPolygons apart = { rectangle(0., 10.), rectangle(20., 10.) };
|
|
||||||
const ExPolygons bridge = { rectangle(0., 30.) };
|
|
||||||
const ExPolygons left = { rectangle(0., 10.) };
|
|
||||||
const ExPolygons right = { rectangle(20., 10.) };
|
|
||||||
size_t count = 0;
|
|
||||||
|
|
||||||
const std::vector<std::vector<size_t>> stacked = connected_bodies({ &apart, &apart }, count);
|
|
||||||
CHECK(count == 2);
|
|
||||||
CHECK(stacked[1][0] == stacked[0][0]);
|
|
||||||
CHECK(stacked[1][1] == stacked[0][1]);
|
|
||||||
|
|
||||||
connected_bodies({ &apart, &bridge, &apart }, count);
|
|
||||||
CHECK(count == 1);
|
|
||||||
|
|
||||||
// Neighbors that do not overlap stay apart even with one island a layer.
|
|
||||||
connected_bodies({ &left, &right }, count);
|
|
||||||
CHECK(count == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("The island locator tests the outlines only where boxes overlap", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const auto square = [](double from, double to) {
|
|
||||||
return Polygon::new_scale({ { from, from }, { to, from }, { to, to }, { from, to } });
|
|
||||||
};
|
|
||||||
Polygon hole = square(5., 25.);
|
|
||||||
hole.make_clockwise();
|
|
||||||
ExPolygon ring(square(0., 30.));
|
|
||||||
ring.holes.emplace_back(hole);
|
|
||||||
ExPolygon alone(square(40., 50.));
|
|
||||||
const ExPolygons islands = { ring, ExPolygon(square(10., 20.)), alone };
|
|
||||||
const IslandLocator locator(islands, scaled<coord_t>(1.));
|
|
||||||
const auto at = [](double x, double y) { return Point::new_scale(x, y); };
|
|
||||||
|
|
||||||
CHECK(locator.find(at(2., 2.)).first == 0);
|
|
||||||
CHECK(locator.find(at(15., 15.)).first == 1);
|
|
||||||
// In the ring's hole, outside the island within it, the nearest outline counts.
|
|
||||||
CHECK(locator.find(at(7., 15.)).first == 0);
|
|
||||||
CHECK(locator.find(at(9.5, 15.)).first == 1);
|
|
||||||
// An island no other box reaches takes the margin past its outline.
|
|
||||||
CHECK(locator.find(at(50.5, 45.)).first == 2);
|
|
||||||
// Unless strict, as for an island whose neighbor is another instance's: then it is only the nearest, 0.5 mm away.
|
|
||||||
const auto [nearest, distance] = locator.find(at(50.5, 45.), true);
|
|
||||||
CHECK(nearest == 2);
|
|
||||||
CHECK_THAT(distance, WithinRel(sqr(scaled<double>(0.5)), 1e-6));
|
|
||||||
CHECK_FALSE(locator.holds(2, at(50.5, 45.), true));
|
|
||||||
CHECK(locator.holds(2, at(50.5, 45.)));
|
|
||||||
CHECK_FALSE(locator.holds(1, at(7., 15.)));
|
|
||||||
CHECK(locator.find(at(35., 45.)).first == -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Separate solids are separate bodies", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
|
||||||
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() }, { &cube, Geometry::translation_transform({ 20., 0., 0. }) } }, { 1., 1. }, {}, 100);
|
|
||||||
|
|
||||||
REQUIRE(bodies.size() == 2);
|
|
||||||
CHECK_THAT(bodies[0].mass, WithinRel(1000., 1e-4));
|
|
||||||
CHECK_THAT((bodies[0].center - Vec3d(5., 5., 5.)).norm(), WithinAbs(0., 1e-4));
|
|
||||||
CHECK_THAT(bodies[1].mass, WithinRel(1000., 1e-4));
|
|
||||||
CHECK_THAT((bodies[1].center - Vec3d(25., 5., 5.)).norm(), WithinAbs(0., 1e-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Overlapping solids are one body that counts the overlap once", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
|
||||||
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() }, { &cube, Geometry::translation_transform({ 5., 0., 0. }) } }, { 1., 1. }, {}, 100);
|
|
||||||
|
|
||||||
// Their union is a 15 x 10 x 10 box, which spreads a^2 / 12 along each side a.
|
|
||||||
REQUIRE(bodies.size() == 1);
|
|
||||||
const SolidBody &body = bodies.front();
|
|
||||||
CHECK_THAT(body.mass, WithinRel(1500., 1e-4));
|
|
||||||
CHECK_THAT(body.volume, WithinRel(1500., 1e-4));
|
|
||||||
CHECK_THAT((body.center - Vec3d(7.5, 5., 5.)).norm(), WithinAbs(0., 1e-4));
|
|
||||||
const Matrix3d spread = Vec3d(225., 100., 100.).asDiagonal() * (1. / 12.);
|
|
||||||
CHECK_THAT((body.spread - spread).norm(), WithinAbs(0., 1e-4));
|
|
||||||
|
|
||||||
// Turned a quarter about z, the box spans what was its y in -x.
|
|
||||||
const BoundingBoxf3 box = body.bounding_box(Geometry::rotation_transform({ 0., 0., 0.5 * PI }));
|
|
||||||
CHECK_THAT((box.min - Vec3d(-10., 0., 0.)).norm(), WithinAbs(0., 1e-4));
|
|
||||||
CHECK_THAT((box.max - Vec3d(0., 15., 10.)).norm(), WithinAbs(0., 1e-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A negative solid is cut away from the body", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
|
||||||
const indexed_triangle_set notch = its_make_cube(4., 4., 4.);
|
|
||||||
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() } }, { 1. }, { { ¬ch, Transform3d::Identity() } }, 100);
|
|
||||||
// A 10 mm cube centered at 5 less a 4 mm cube centered at 2, in each axis alike.
|
|
||||||
const double expected = (1000. * 5. - 64. * 2.) / (1000. - 64.);
|
|
||||||
|
|
||||||
REQUIRE(bodies.size() == 1);
|
|
||||||
CHECK_THAT(bodies[0].mass, WithinRel(1000. - 64., 1e-4));
|
|
||||||
CHECK_THAT((bodies[0].center - Vec3d(expected, expected, expected)).norm(), WithinAbs(0., 1e-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Each solid weighs its density, the later of two overlapping ones the overlap", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
|
||||||
const MeshInPlace left{ &cube, Transform3d::Identity() };
|
|
||||||
const MeshInPlace right{ &cube, Geometry::translation_transform({ 5., 0., 0. }) };
|
|
||||||
|
|
||||||
// The right cube, three times as dense, prints the overlap from x 5 to 10.
|
|
||||||
auto bodies = solid_bodies({ left, right }, { 1., 3. }, {}, 100);
|
|
||||||
REQUIRE(bodies.size() == 1);
|
|
||||||
CHECK_THAT(bodies[0].mass, WithinRel(500. + 3. * 1000., 1e-4));
|
|
||||||
CHECK_THAT(bodies[0].volume, WithinRel(1500., 1e-4));
|
|
||||||
CHECK_THAT(bodies[0].center.x(), WithinAbs((500. * 2.5 + 3000. * 10.) / 3500., 1e-4));
|
|
||||||
|
|
||||||
// Listed the other way round, the left cube prints it.
|
|
||||||
bodies = solid_bodies({ right, left }, { 3., 1. }, {}, 100);
|
|
||||||
REQUIRE(bodies.size() == 1);
|
|
||||||
CHECK_THAT(bodies[0].mass, WithinRel(1000. + 3. * 500., 1e-4));
|
|
||||||
CHECK_THAT(bodies[0].center.x(), WithinAbs((1000. * 5. + 1500. * 12.5) / 2500., 1e-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Separate solids weigh their own densities", "[ConnectedBodies]")
|
|
||||||
{
|
|
||||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
|
||||||
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() }, { &cube, Geometry::translation_transform({ 20., 0., 0. }) } },
|
|
||||||
{ 1.24, 2. }, {}, 100);
|
|
||||||
|
|
||||||
REQUIRE(bodies.size() == 2);
|
|
||||||
CHECK_THAT(bodies[0].mass, WithinRel(1240., 1e-4));
|
|
||||||
CHECK_THAT(bodies[1].mass, WithinRel(2000., 1e-4));
|
|
||||||
}
|
|
||||||
@@ -255,39 +255,3 @@ TEST_CASE("blend_color_multi weights components", "[FilamentMixer]")
|
|||||||
REQUIRE(std::abs(comp(mixed, i) - comp("#123456", i)) <= 8);
|
REQUIRE(std::abs(comp(mixed, i) - comp("#123456", i)) <= 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("format_mixed_ratios normalises weights to four decimals", "[FilamentMixer]")
|
|
||||||
{
|
|
||||||
REQUIRE(format_mixed_components({1, 3}) == "1,3");
|
|
||||||
REQUIRE(format_mixed_ratios({50, 50}) == "0.5000,0.5000");
|
|
||||||
REQUIRE(format_mixed_ratios({1, 2}) == "0.3333,0.6667");
|
|
||||||
REQUIRE(format_mixed_ratios({1, 1}) == format_mixed_ratios({50, 50}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("find_fixed_mixed_filament reuses only a fixed slot of the same blend", "[FilamentMixer]")
|
|
||||||
{
|
|
||||||
// Physical slots 0 and 1; slot 2 blends them 50:50 as a gradient, slot 3 at a fixed 50:50.
|
|
||||||
DynamicPrintConfig cfg;
|
|
||||||
cfg.set_key_value("filament_is_mixed", new ConfigOptionBools({false, false, true, true}));
|
|
||||||
cfg.set_key_value("filament_mixed_components", new ConfigOptionStrings({"", "", "1,2", "1,2"}));
|
|
||||||
cfg.set_key_value("filament_mixed_sublayer_ratios",
|
|
||||||
new ConfigOptionStrings({"", "", format_mixed_ratios({50, 50}), format_mixed_ratios({50, 50})}));
|
|
||||||
cfg.set_key_value("filament_mixed_gradient", new ConfigOptionBools({false, false, true, false}));
|
|
||||||
|
|
||||||
SECTION("The fixed slot is found, whatever scale the weights are given at") {
|
|
||||||
REQUIRE(find_fixed_mixed_filament(cfg, {1, 2}, {50, 50}) == 3);
|
|
||||||
REQUIRE(find_fixed_mixed_filament(cfg, {1, 2}, {1, 1}) == 3);
|
|
||||||
}
|
|
||||||
SECTION("A gradient slot with the same components and ratios is not a match") {
|
|
||||||
cfg.option<ConfigOptionBools>("filament_is_mixed")->values[3] = false;
|
|
||||||
REQUIRE(find_fixed_mixed_filament(cfg, {1, 2}, {50, 50}) == -1);
|
|
||||||
}
|
|
||||||
SECTION("A project without the gradient key still matches its fixed slots") {
|
|
||||||
cfg.erase("filament_mixed_gradient");
|
|
||||||
REQUIRE(find_fixed_mixed_filament(cfg, {1, 2}, {50, 50}) == 2);
|
|
||||||
}
|
|
||||||
SECTION("Another ratio or another component order is a different blend") {
|
|
||||||
REQUIRE(find_fixed_mixed_filament(cfg, {1, 2}, {1, 2}) == -1);
|
|
||||||
REQUIRE(find_fixed_mixed_filament(cfg, {2, 1}, {50, 50}) == -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,16 +12,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <catch2/matchers/catch_matchers.hpp>
|
|
||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
||||||
#include "libslic3r/Geometry.hpp"
|
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
|
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using Catch::Matchers::WithinAbs;
|
|
||||||
using Catch::Matchers::WithinRel;
|
|
||||||
|
|
||||||
TEST_CASE("Split empty mesh", "[its_split][its]") {
|
TEST_CASE("Split empty mesh", "[its_split][its]") {
|
||||||
|
|
||||||
@@ -322,75 +317,3 @@ TEST_CASE("Simplified cube should not be empty.", "[its]")
|
|||||||
its_quadric_edge_collapse(its, wanted_count, &max_error);
|
its_quadric_edge_collapse(its, wanted_count, &max_error);
|
||||||
CHECK(!its.indices.empty());
|
CHECK(!its.indices.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("A box far from the origin has its center of mass at its center and spreads as a box", "[its]")
|
|
||||||
{
|
|
||||||
indexed_triangle_set box = its_make_cube(10., 20., 30.);
|
|
||||||
for (Vec3f &v : box.vertices)
|
|
||||||
v += Vec3f(1000.f, 2000.f, 300.f);
|
|
||||||
const MassProperties solid = its_mass_properties(box);
|
|
||||||
CHECK_THAT(solid.volume, WithinRel(10. * 20. * 30., 1e-6));
|
|
||||||
CHECK_THAT(solid.mass, WithinRel(solid.volume, 1e-12));
|
|
||||||
CHECK_THAT(solid.center.x(), WithinAbs(1005., 1e-6));
|
|
||||||
CHECK_THAT(solid.center.y(), WithinAbs(2010., 1e-6));
|
|
||||||
CHECK_THAT(solid.center.z(), WithinAbs(315., 1e-6));
|
|
||||||
// A box of side a spreads a^2 / 12 along it.
|
|
||||||
const Matrix3d spread = Vec3d(100., 400., 900.).asDiagonal() * (1. / 12.);
|
|
||||||
CHECK_THAT((solid.spread - spread).norm(), WithinAbs(0., 1e-6));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("The center of mass of a cone lies a quarter of its height above the base", "[its]")
|
|
||||||
{
|
|
||||||
// Neither the surface centroid nor the vertex average lands there.
|
|
||||||
const double h = 40.;
|
|
||||||
const MassProperties solid = its_mass_properties(its_make_cone(10., h));
|
|
||||||
CHECK(solid.volume > 0.);
|
|
||||||
CHECK_THAT(solid.center.z(), WithinAbs(h / 4., 1e-4));
|
|
||||||
CHECK_THAT(solid.center.x(), WithinAbs(0., 1e-4));
|
|
||||||
CHECK_THAT(solid.center.y(), WithinAbs(0., 1e-4));
|
|
||||||
// 3 h^2 / 80 along the axis.
|
|
||||||
CHECK_THAT(solid.spread(2, 2), WithinRel(3. * h * h / 80., 1e-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A cavity moves the center of mass away from it", "[its]")
|
|
||||||
{
|
|
||||||
indexed_triangle_set solid = its_make_cube(20., 20., 20.);
|
|
||||||
indexed_triangle_set cavity = its_make_cube(10., 10., 8.);
|
|
||||||
for (Vec3f &v : cavity.vertices)
|
|
||||||
v += Vec3f(5.f, 5.f, 10.f);
|
|
||||||
its_flip_triangles(cavity);
|
|
||||||
its_merge(solid, cavity);
|
|
||||||
const MassProperties hollow = its_mass_properties(solid);
|
|
||||||
// A 20 mm cube centered at z 10 less a 10x10x8 mm cavity centered at z 14.
|
|
||||||
CHECK_THAT(hollow.volume, WithinRel(8000. - 800., 1e-6));
|
|
||||||
CHECK_THAT(hollow.center.x(), WithinAbs(10., 1e-6));
|
|
||||||
CHECK_THAT(hollow.center.y(), WithinAbs(10., 1e-6));
|
|
||||||
CHECK_THAT(hollow.center.z(), WithinAbs((8000. * 10. - 800. * 14.) / (8000. - 800.), 1e-6));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("The mass properties follow an affine transformation of the mesh", "[its]")
|
|
||||||
{
|
|
||||||
indexed_triangle_set cone = its_make_cone(10., 40.);
|
|
||||||
const MassProperties solid = its_mass_properties(cone);
|
|
||||||
const Transform3d trafo = Geometry::translation_transform({ 50., -20., 7. }) * Geometry::rotation_transform({ 0.3, -0.5, 1.2 }) *
|
|
||||||
Geometry::scale_transform({ 2., 0.5, 1.5 });
|
|
||||||
for (Vec3f &v : cone.vertices)
|
|
||||||
v = (trafo * v.cast<double>()).cast<float>();
|
|
||||||
const MassProperties moved = its_mass_properties(cone);
|
|
||||||
const MassProperties expected = solid.transformed(trafo);
|
|
||||||
CHECK_THAT(moved.volume, WithinRel(expected.volume, 1e-5));
|
|
||||||
CHECK_THAT(moved.mass, WithinRel(expected.mass, 1e-5));
|
|
||||||
CHECK_THAT((moved.center - expected.center).norm(), WithinAbs(0., 1e-4));
|
|
||||||
CHECK_THAT((moved.spread - expected.spread).norm(), WithinAbs(0., 1e-3));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Flipped faces keep the mass properties", "[its]")
|
|
||||||
{
|
|
||||||
indexed_triangle_set cone = its_make_cone(10., 40.);
|
|
||||||
const MassProperties solid = its_mass_properties(cone);
|
|
||||||
its_flip_triangles(cone);
|
|
||||||
const MassProperties flipped = its_mass_properties(cone);
|
|
||||||
CHECK_THAT(flipped.volume, WithinRel(solid.volume, 1e-9));
|
|
||||||
CHECK_THAT((flipped.center - solid.center).norm(), WithinAbs(0., 1e-9));
|
|
||||||
CHECK_THAT((flipped.spread - solid.spread).norm(), WithinAbs(0., 1e-9));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5888,21 +5888,6 @@ TEST_CASE("A project saved with pressure advance per filament applies it to ever
|
|||||||
check_double_vector(petg.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.97 });
|
check_double_vector(petg.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.97 });
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("A multi-toolhead project saved without filament self indices loads every filament", "[Preset][Bundle]")
|
|
||||||
{
|
|
||||||
const std::vector<std::string> colors = { "#FF0000", "#000000", "#FFFFFF", "#FFFF00" };
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.opt<ConfigOptionStrings>("filament_colour")->values = colors;
|
|
||||||
config.opt<ConfigOptionFloats>("nozzle_diameter")->values = std::vector<double>(colors.size(), 0.4);
|
|
||||||
config.option<ConfigOptionBool>("single_extruder_multi_material")->value = false;
|
|
||||||
Preset::normalize(config);
|
|
||||||
|
|
||||||
PresetBundle bundle;
|
|
||||||
REQUIRE_NOTHROW(bundle.load_config_model("test.3mf", std::move(config)));
|
|
||||||
CHECK(bundle.filament_presets.size() == colors.size());
|
|
||||||
CHECK(bundle.project_config.opt<ConfigOptionStrings>("filament_colour")->values == colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A system preset resolves by name from the bundled profiles", "[Preset][Bundle]")
|
TEST_CASE("A system preset resolves by name from the bundled profiles", "[Preset][Bundle]")
|
||||||
{
|
{
|
||||||
ScopedTemporaryDir temp_dir;
|
ScopedTemporaryDir temp_dir;
|
||||||
|
|||||||
@@ -68,28 +68,6 @@ static std::shared_ptr<std::vector<unsigned char>> make_flat_gray_png(uint8_t va
|
|||||||
return std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
return std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same round trip for a flat colour image, which decode_height_texture() reads through its colour path.
|
|
||||||
static std::shared_ptr<std::vector<unsigned char>> make_flat_rgb_png(uint8_t r, uint8_t g, uint8_t b, size_t w = 4, size_t h = 4)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> rgb;
|
|
||||||
for (size_t i = 0; i < w * h; ++i)
|
|
||||||
rgb.insert(rgb.end(), { r, g, b });
|
|
||||||
const boost::filesystem::path tmp_path = boost::filesystem::temp_directory_path()
|
|
||||||
/ boost::filesystem::unique_path("texdisp_test_%%%%%%%%.png");
|
|
||||||
REQUIRE(Slic3r::png::write_rgb_to_file(tmp_path.string(), w, h, rgb));
|
|
||||||
|
|
||||||
std::vector<unsigned char> bytes;
|
|
||||||
{
|
|
||||||
std::ifstream ifs(tmp_path.string(), std::ios::binary);
|
|
||||||
bytes.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
|
||||||
}
|
|
||||||
boost::system::error_code ec;
|
|
||||||
boost::filesystem::remove(tmp_path, ec);
|
|
||||||
|
|
||||||
REQUIRE_FALSE(bytes.empty());
|
|
||||||
return std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
// A hard-edged black/white checkerboard, the worst case for a height map: every texel boundary is a
|
// A hard-edged black/white checkerboard, the worst case for a height map: every texel boundary is a
|
||||||
// step, which is precisely the relief the post-process smoothing exists to round off.
|
// step, which is precisely the relief the post-process smoothing exists to round off.
|
||||||
static std::shared_ptr<std::vector<unsigned char>> make_checkerboard_png(size_t w = 16, size_t h = 16)
|
static std::shared_ptr<std::vector<unsigned char>> make_checkerboard_png(size_t w = 16, size_t h = 16)
|
||||||
@@ -2157,20 +2135,3 @@ TEST_CASE("A second bake beside a first comes out as fine as a single bake", "[T
|
|||||||
CHECK(second <= single * 5 / 4);
|
CHECK(second <= single * 5 / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("whether a layer's texture has colour agrees with its decode", "[TextureDisplacement]")
|
|
||||||
{
|
|
||||||
TextureDisplacementLayer gray;
|
|
||||||
gray.image_data = make_flat_gray_png(128);
|
|
||||||
CHECK_FALSE(height_texture_has_color(gray));
|
|
||||||
|
|
||||||
TextureDisplacementLayer color;
|
|
||||||
color.image_data = make_flat_rgb_png(200, 40, 10);
|
|
||||||
// Before the image is decoded and after, and whatever the smoothing.
|
|
||||||
CHECK(height_texture_has_color(color));
|
|
||||||
CHECK(decode_height_texture(color).has_color());
|
|
||||||
CHECK(height_texture_has_color(color));
|
|
||||||
color.smoothing = 0.5f;
|
|
||||||
CHECK(height_texture_has_color(color));
|
|
||||||
|
|
||||||
CHECK_FALSE(height_texture_has_color(TextureDisplacementLayer{}));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_plugin_audit.cpp
|
test_plugin_audit.cpp
|
||||||
test_plugin_json_depth.cpp
|
test_plugin_json_depth.cpp
|
||||||
test_shortcuts.cpp
|
test_shortcuts.cpp
|
||||||
test_texture_color_palette.cpp
|
|
||||||
test_file_url.cpp
|
test_file_url.cpp
|
||||||
test_user_manager.cpp
|
test_user_manager.cpp
|
||||||
../fff_print/test_helpers.cpp
|
../fff_print/test_helpers.cpp
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ TEST_CASE("Plugin host API reports unavailable GUI objects before Orca app initi
|
|||||||
{
|
{
|
||||||
py::object host = import_orca_module().attr("host");
|
py::object host = import_orca_module().attr("host");
|
||||||
|
|
||||||
for (const char* function_name : { "preset_bundle", "plater", "model" }) {
|
for (const char* function_name : { "preset_bundle", "plater", "model", "app_info", "selected_printer" }) {
|
||||||
CAPTURE(function_name);
|
CAPTURE(function_name);
|
||||||
try {
|
try {
|
||||||
host.attr(function_name)();
|
host.attr(function_name)();
|
||||||
@@ -145,6 +145,17 @@ TEST_CASE("Plugin host API reports unavailable GUI objects before Orca app initi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Plugin host API reports no GL details before the 3D view exists", "[PluginHost][Python]")
|
||||||
|
{
|
||||||
|
py::object host = import_orca_module().attr("host");
|
||||||
|
|
||||||
|
CHECK(host.attr("gl_info")().is_none());
|
||||||
|
|
||||||
|
py::object plater_type = host.attr("Plater");
|
||||||
|
CHECK(has_attr(plater_type, "project_path"));
|
||||||
|
CHECK(has_attr(plater_type, "export_3mf_copy"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Plugin host API exposes the UI module and guards it before Orca app initialization", "[PluginHost][Python]")
|
TEST_CASE("Plugin host API exposes the UI module and guards it before Orca app initialization", "[PluginHost][Python]")
|
||||||
{
|
{
|
||||||
py::object host = import_orca_module().attr("host");
|
py::object host = import_orca_module().attr("host");
|
||||||
|
|||||||
@@ -1,294 +0,0 @@
|
|||||||
// The texture displacement gizmo's palette helpers live in libslic3r_gui; this is the suite that links it.
|
|
||||||
// Same Windows include prologue as test_filament_bitmap_utils.cpp (wx pulls in <windows.h>; keep
|
|
||||||
// WIN32_LEAN_AND_MEAN / NOMINMAX ahead of the Catch2 headers).
|
|
||||||
#ifdef WIN32
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX
|
|
||||||
#endif
|
|
||||||
#include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <fstream>
|
|
||||||
#include <ios>
|
|
||||||
#include <iterator>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <catch2/catch_all.hpp>
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include <catch2/matchers/catch_matchers.hpp>
|
|
||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
||||||
|
|
||||||
#include "libslic3r/Color.hpp"
|
|
||||||
#include "libslic3r/FilamentMixer.hpp"
|
|
||||||
#include "libslic3r/PNGReadWrite.hpp"
|
|
||||||
#include "libslic3r/Point.hpp"
|
|
||||||
#include "libslic3r/TextureDisplacement.hpp"
|
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
|
||||||
#include "libslic3r/TriangleSelector.hpp"
|
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp"
|
|
||||||
#include "slic3r/Utils/ColorSpaceConvert.hpp"
|
|
||||||
#include "test_utils.hpp"
|
|
||||||
|
|
||||||
using namespace Slic3r;
|
|
||||||
using Catch::Matchers::WithinAbs;
|
|
||||||
using Gizmo = Slic3r::GUI::GLGizmoTextureDisplacement;
|
|
||||||
using Entry = Gizmo::PaletteEntry;
|
|
||||||
using MixTarget = Gizmo::MixTarget;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
const ColorRGBA BLACK{ 0.f, 0.f, 0.f, 1.f };
|
|
||||||
const ColorRGBA WHITE{ 1.f, 1.f, 1.f, 1.f };
|
|
||||||
const ColorRGBA RED{ 1.f, 0.f, 0.f, 1.f };
|
|
||||||
const ColorRGBA BLUE{ 0.f, 0.f, 1.f, 1.f };
|
|
||||||
const ColorRGBA YELLOW{ 1.f, 1.f, 0.f, 1.f };
|
|
||||||
|
|
||||||
MixTarget target(const Vec3f &rgb, float weight)
|
|
||||||
{
|
|
||||||
MixTarget t;
|
|
||||||
RGB2Lab(rgb.x(), rgb.y(), rgb.z(), &t.lab.x(), &t.lab.y(), &t.lab.z());
|
|
||||||
t.weight = weight;
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The colour a mixed slot of these two filaments shows, as the sidebar computes it.
|
|
||||||
Vec3f slot_color(const ColorRGBA &a, const ColorRGBA &b, int a_percent)
|
|
||||||
{
|
|
||||||
ColorRGB c;
|
|
||||||
REQUIRE(decode_color(blend_color_multi({ encode_color(a), encode_color(b) }, { a_percent, 100 - a_percent }), c));
|
|
||||||
return Vec3f(c.r(), c.g(), c.b());
|
|
||||||
}
|
|
||||||
|
|
||||||
// A colour image layer, through Slic3r's own PNG writer so decode_height_texture() reads it the way it
|
|
||||||
// reads an imported texture.
|
|
||||||
TextureDisplacementLayer color_layer(int w, int h, const std::vector<uint8_t> &rgb)
|
|
||||||
{
|
|
||||||
ScopedTemporaryFile png(".png");
|
|
||||||
REQUIRE(png::write_rgb_to_file(png.string(), size_t(w), size_t(h), rgb));
|
|
||||||
std::ifstream in(png.string(), std::ios::binary);
|
|
||||||
std::vector<unsigned char> bytes{ std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>() };
|
|
||||||
REQUIRE_FALSE(bytes.empty());
|
|
||||||
|
|
||||||
TextureDisplacementLayer layer;
|
|
||||||
layer.image_data = std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
|
||||||
layer.color_enabled = true;
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A red/green ramp over a fixed blue: colours spread over many bins, so the image is not flat-colour.
|
|
||||||
TextureDisplacementLayer gradient_layer()
|
|
||||||
{
|
|
||||||
const int n = 64;
|
|
||||||
std::vector<uint8_t> rgb;
|
|
||||||
for (int y = 0; y < n; ++y)
|
|
||||||
for (int x = 0; x < n; ++x) {
|
|
||||||
rgb.push_back(uint8_t(x * 4));
|
|
||||||
rgb.push_back(uint8_t(y * 4));
|
|
||||||
rgb.push_back(128);
|
|
||||||
}
|
|
||||||
return color_layer(n, n, rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A 2048x1100 image, over mix_targets()' sampling budget, that is pure red wherever `red(x, y)` holds and
|
|
||||||
// elsewhere a gradient spread over far more than eight coarse bins, so the image is not flat-colour.
|
|
||||||
template<class RedFn> TextureDisplacementLayer striped_layer(RedFn red)
|
|
||||||
{
|
|
||||||
const int w = 2048, h = 1100;
|
|
||||||
std::vector<uint8_t> rgb;
|
|
||||||
rgb.reserve(size_t(w) * size_t(h) * 3);
|
|
||||||
for (int y = 0; y < h; ++y)
|
|
||||||
for (int x = 0; x < w; ++x) {
|
|
||||||
const bool is_red = red(x, y);
|
|
||||||
rgb.push_back(is_red ? 255 : uint8_t(x * 255 / w));
|
|
||||||
rgb.push_back(is_red ? 0 : uint8_t(y * 255 / h));
|
|
||||||
rgb.push_back(is_red ? 0 : 128);
|
|
||||||
}
|
|
||||||
return color_layer(w, h, rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// How much of the targets' weight is pure red.
|
|
||||||
float red_weight(const std::vector<MixTarget> &targets)
|
|
||||||
{
|
|
||||||
Vec3f red;
|
|
||||||
RGB2Lab(1.f, 0.f, 0.f, &red.x(), &red.y(), &red.z());
|
|
||||||
float weight = 0.f;
|
|
||||||
for (const MixTarget &t : targets)
|
|
||||||
if ((t.lab - red).norm() < 3.f)
|
|
||||||
weight += t.weight;
|
|
||||||
return weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("mix targets cover each colouring photo layer once and skip the rest", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
TextureDisplacementLayer photo = gradient_layer();
|
|
||||||
|
|
||||||
const std::vector<MixTarget> targets = Gizmo::mix_targets({ photo });
|
|
||||||
REQUIRE_FALSE(targets.empty());
|
|
||||||
float total = 0.f;
|
|
||||||
for (const MixTarget &t : targets)
|
|
||||||
total += t.weight;
|
|
||||||
REQUIRE_THAT(total, WithinAbs(1., 1e-4));
|
|
||||||
|
|
||||||
SECTION("A layer that does not colour gives no targets") {
|
|
||||||
photo.color_enabled = false;
|
|
||||||
REQUIRE(Gizmo::mix_targets({ photo }).empty());
|
|
||||||
}
|
|
||||||
SECTION("A flat-colour image prints in single filaments, so it gives no targets") {
|
|
||||||
const TextureDisplacementLayer flat = color_layer(8, 8, std::vector<uint8_t>(8 * 8 * 3, 200));
|
|
||||||
REQUIRE(Gizmo::mix_targets({ flat }).empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("mix targets of a large image weigh each colour by its share, whatever its stripes", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
// Sampled rather than read in full, these must not line up with the samples: a fixed sampling step
|
|
||||||
// sees only one phase of a stripe pattern, and a generator whose offsets repeat sees only some.
|
|
||||||
SECTION("Red on every other column") {
|
|
||||||
CHECK_THAT(red_weight(Gizmo::mix_targets({ striped_layer([](int x, int) { return x % 2 == 0; }) })),
|
|
||||||
WithinAbs(1. / 2., 0.03));
|
|
||||||
}
|
|
||||||
SECTION("Red on every fourth column") {
|
|
||||||
CHECK_THAT(red_weight(Gizmo::mix_targets({ striped_layer([](int x, int) { return x % 4 == 0; }) })),
|
|
||||||
WithinAbs(1. / 4., 0.03));
|
|
||||||
}
|
|
||||||
SECTION("Red on diagonals") {
|
|
||||||
CHECK_THAT(red_weight(Gizmo::mix_targets({ striped_layer([](int x, int y) { return (x - y) % 3 == 0; }) })),
|
|
||||||
WithinAbs(1. / 3., 0.03));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("the mix ranked first is the one the image needs most", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
// The whole image is exactly the colour of a 1:1 black/white slot.
|
|
||||||
const std::vector<MixTarget> targets = { target(slot_color(BLACK, WHITE, 50), 1.f) };
|
|
||||||
const std::vector<Entry> ranked = Gizmo::rank_mixes({ BLACK, WHITE }, targets, 1);
|
|
||||||
REQUIRE(ranked.size() == 1);
|
|
||||||
CHECK(ranked.front().a == 0);
|
|
||||||
CHECK(ranked.front().b == 1);
|
|
||||||
CHECK(ranked.front().num * 2 == ranked.front().den);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("an image the filaments already match ranks no mixes", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
const std::vector<MixTarget> targets = { target(Vec3f(1.f, 0.f, 0.f), 0.5f), target(Vec3f(0.f, 0.f, 1.f), 0.5f) };
|
|
||||||
REQUIRE(Gizmo::rank_mixes({ RED, BLUE }, targets, 8).empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("ranked mixes stay within the limit and show their slot's colour", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
const std::vector<ColorRGBA> filaments = { RED, BLUE, YELLOW };
|
|
||||||
const std::vector<MixTarget> targets = Gizmo::mix_targets({ gradient_layer() });
|
|
||||||
|
|
||||||
for (const int limit : { 1, 3 }) {
|
|
||||||
const std::vector<Entry> ranked = Gizmo::rank_mixes(filaments, targets, limit);
|
|
||||||
CHECK(int(ranked.size()) <= limit);
|
|
||||||
CHECK_FALSE(ranked.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<Entry> ranked = Gizmo::rank_mixes(filaments, targets, 6);
|
|
||||||
for (const Entry &e : ranked) {
|
|
||||||
REQUIRE(e.is_mix());
|
|
||||||
const Vec3f expected = slot_color(filaments[size_t(e.a)], filaments[size_t(e.b)], e.a_percent());
|
|
||||||
CHECK_THAT(e.rgb.x(), WithinAbs(expected.x(), 1e-6));
|
|
||||||
CHECK_THAT(e.rgb.y(), WithinAbs(expected.y(), 1e-6));
|
|
||||||
CHECK_THAT(e.rgb.z(), WithinAbs(expected.z(), 1e-6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("picked mixes never need more slots than the project has free", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
const std::vector<Entry> ranking = { { Vec3f::Zero(), 0, 1, 1, 2 }, { Vec3f::Zero(), 0, 1, 1, 3 }, { Vec3f::Zero(), 0, 1, 2, 3 } };
|
|
||||||
const auto reusable_second = [](const Entry &e) { return e.num == 1 && e.den == 3; };
|
|
||||||
|
|
||||||
SECTION("The count caps the pick") {
|
|
||||||
REQUIRE(Gizmo::pick_mixes(ranking, 2, 10, nullptr).size() == 2);
|
|
||||||
}
|
|
||||||
SECTION("With no free slot only a mix that already has one is kept") {
|
|
||||||
const std::vector<Entry> picked = Gizmo::pick_mixes(ranking, 3, 0, reusable_second);
|
|
||||||
REQUIRE(picked.size() == 1);
|
|
||||||
CHECK(picked.front().den == 3);
|
|
||||||
CHECK(picked.front().num == 1);
|
|
||||||
}
|
|
||||||
SECTION("A reusable mix costs no free slot") {
|
|
||||||
const std::vector<Entry> picked = Gizmo::pick_mixes(ranking, 3, 1, reusable_second);
|
|
||||||
REQUIRE(picked.size() == 2);
|
|
||||||
CHECK(picked[0].den == 2);
|
|
||||||
CHECK(picked[1].den == 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("a baked mix paints the slot it was given, wherever that slot sits", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
// Two filaments, then two mixes of them. Palette index 2 is a mix, but its slot need not be
|
|
||||||
// filament 2: a project that already holds other mixed slots puts it further along.
|
|
||||||
const std::vector<Entry> palette = Gizmo::make_palette({ BLACK, WHITE }, { { Vec3f::Zero(), 0, 1, 1, 2 }, { Vec3f::Zero(), 0, 1, 1, 3 } });
|
|
||||||
REQUIRE(palette.size() == 4);
|
|
||||||
// Filament 0, the 1:1 mix twice, nothing: the 1:2 mix is never used.
|
|
||||||
const std::vector<uint8_t> triangle_color = { 1, 3, 3, 0 };
|
|
||||||
|
|
||||||
std::vector<Entry> asked;
|
|
||||||
const auto slot_seven = [&asked](const Entry &e) {
|
|
||||||
asked.push_back(e);
|
|
||||||
return 7;
|
|
||||||
};
|
|
||||||
const std::vector<int> filament = Gizmo::palette_filaments(palette, triangle_color, slot_seven);
|
|
||||||
REQUIRE(filament.size() == 4);
|
|
||||||
CHECK(filament[0] == 0);
|
|
||||||
CHECK(filament[1] == 1);
|
|
||||||
CHECK(filament[2] == 7);
|
|
||||||
CHECK(filament[3] == -1);
|
|
||||||
// Asking creates a slot, so an unused mix is never asked for.
|
|
||||||
REQUIRE(asked.size() == 1);
|
|
||||||
CHECK(asked.front().den == 2);
|
|
||||||
|
|
||||||
SECTION("A mix that gets no slot prints in its dominant component") {
|
|
||||||
const std::vector<int> fallback = Gizmo::palette_filaments(palette, { 3, 4 }, [](const Entry &) { return -1; });
|
|
||||||
CHECK(fallback[2] == 0); // 1:1 - the first component
|
|
||||||
CHECK(fallback[3] == 1); // 1 part black in 3 - white dominates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("the model's colour paint is drawn by filament, and only where no layer paint covers it", "[TextureColorPalette][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
// A strip of four triangles: filament 2 on the first and last, filament 5 on the second, and the third
|
|
||||||
// left to the volume's own filament.
|
|
||||||
indexed_triangle_set strip;
|
|
||||||
strip.vertices = { Vec3f(0, 0, 0), Vec3f(1, 0, 0), Vec3f(0, 1, 0), Vec3f(1, 1, 0), Vec3f(0, 2, 0), Vec3f(1, 2, 0) };
|
|
||||||
strip.indices = { stl_triangle_vertex_indices(0, 1, 2), stl_triangle_vertex_indices(1, 3, 2), stl_triangle_vertex_indices(2, 3, 4),
|
|
||||||
stl_triangle_vertex_indices(3, 5, 4) };
|
|
||||||
const TriangleMesh mesh(strip);
|
|
||||||
TriangleSelector paint(mesh);
|
|
||||||
paint.set_facet(0, EnforcerBlockerType(2));
|
|
||||||
paint.set_facet(1, EnforcerBlockerType(5));
|
|
||||||
paint.set_facet(3, EnforcerBlockerType(2));
|
|
||||||
|
|
||||||
const Gizmo::PaintedColors colors = Gizmo::painted_colors(mesh, paint.serialize());
|
|
||||||
// Grouped by filament; the triangle in the volume's own filament is never drawn.
|
|
||||||
REQUIRE(colors.facets.indices.size() == 3);
|
|
||||||
REQUIRE(colors.source.size() == 3);
|
|
||||||
REQUIRE(colors.state.size() == 3);
|
|
||||||
CHECK(colors.state == std::vector<int>{ 2, 2, 5 });
|
|
||||||
CHECK(colors.source == std::vector<int>{ 0, 3, 1 });
|
|
||||||
|
|
||||||
SECTION("A model triangle a layer's paint covers is left to the preview") {
|
|
||||||
std::vector<bool> excluded(mesh.its.indices.size(), false);
|
|
||||||
excluded[3] = true;
|
|
||||||
const std::vector<size_t> kept = colors.outside(excluded);
|
|
||||||
REQUIRE(kept.size() == 2);
|
|
||||||
CHECK(colors.source[kept[0]] == 0);
|
|
||||||
CHECK(colors.source[kept[1]] == 1);
|
|
||||||
}
|
|
||||||
SECTION("A mask shorter than the model leaves the rest drawn") {
|
|
||||||
CHECK(colors.outside({ true }).size() == 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user