Compare commits

...
Author SHA1 Message Date
Hanif Koh 6d218f9abf Let Plugin Windows Be Maximized on X11
wxGTK types every wxDialog as a dialog window, and Mutter offers no
maximize for anything but a normal window, so the maximize button and
shortcut did nothing on GNOME under X11. Modeless plugin windows are now
normal windows, kept off the taskbar as before. Modal ones stay dialogs
so the window manager keeps routing focus from the main window to them.
2026-10-10 14:27:45 +08:00
Kris Austin 4a20168742 Fix loading a multi-toolhead 3MF that has no filament_self_index (#16331) 2026-10-09 22:07:33 -03:00
Kris Austin 1466c0e57f Fix rare hang in mcut mesh booleans (#16330) 2026-10-09 21:55:06 -03:00
Kris Austin cd02116242 ci: replace the Docker tag action with git commands (#16327) 2026-10-09 19:17:16 -03:00
Kris Austin 35bac4cb69 Remove unused OpenCSG, GLEW and GLU dependencies (#16318) 2026-10-09 16:50:19 -03:00
0f3e8fbf27 Add center of mass markers to Prepare and Preview (#16291)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: Kris Austin <kris.austin@gmail.com>
2026-10-09 16:48:48 -03:00
Misterff1 b5ef24e7ff Fix regression: Arc Fitting setting for BBL P2S (#16319)
Disable Arc Fitting for BBL P2S
2026-10-10 00:32:46 +08:00
Ian Chua 8585eae816 fix: hide symbols of the bundled static openssl (#16317)
* fix: hide symbols of the bundled static openssl

* fix: hide the bundled static OpenSSL symbols on Linux

* fix: relink _ssl/_hashlib when OpenSSL recipe changes
2026-10-10 00:26:02 +08:00
Lam Wei Lun e72ace164b feat(speed-dial): add plugin page capabilities as actions
Plugin Pages capabilities (top-level notebook tabs) were missing from the Speed Dial because ActionRegistry only ingested Script capabilities.

Enumerate and subscribe to Pages as well. Launching a page action switches the notebook to that page, swapping it into the visible slot first when it lives behind the overflow dropdown.
2026-10-09 23:44:52 +08:00
Ian BassiandRodrigo Faselli b5f5b50157 Adaptive TPMS (#16005)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-09 12:36:09 -03:00
e0b35f9ec9 Fix garbled G-code preview when a post-processing script is used (#15005)
* Rebuild the G-code line offsets after post-processing scripts run in place

* Clamp the G-code window reads to the mapped file size

* Add tests for rebuilding the G-code line offsets

* Include <mutex>, <ios> and boost/filesystem/operations.hpp where they are used

* Keep the preview's G-code lines and highlight in step with post-processing scripts

---------

Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-10-09 23:08:43 +08:00
Ian Chua eb28daf0fe fix: persist user id so that setting_id doesn't get wiped (#16246) 2026-10-09 21:00:27 +08:00
ExPikaPaka 6ac7ae24a3 Block undo and redo while a background job runs (#16015)
* Block undo and redo while a background job runs

A job is queued against the model as it stands and hands its result back
when it finishes, so undoing underneath it leaves that result landing on
geometry it was never computed for. Undo and redo now wait for the job
and say so, and can_undo()/can_redo() report the same, so the toolbar
and the menu items stay in step.

* Say what to do about the running operation, not just that it blocks

Review feedback: "Stop it first" is the only way out the old text offered,
and stopping is rarely what the user wants. Waiting for the operation to
finish works just as well, so the notification now names both.
2026-10-09 19:26:37 +08:00
ExPikaPaka d55e32fed4 Stop reading a missing AMF metadata type as a string (#16060)
A `<metadata>` element without a `type` attribute makes
`get_attribute()` return nullptr, which was then assigned to a
`std::string` and read as a C string.

Check it the way the sibling metadata handler already does, and stop the
parse.

Regression test in `tests/libslic3r/test_amf.cpp`, a new file: the suite
had no AMF test at all.
2026-10-09 19:25:53 +08:00
SoftFever cd0b529e31 Reject 3MF component references that form a cycle (#16059) 2026-10-09 19:23:20 +08:00
113 changed files with 4394 additions and 67638 deletions
+3 -6
View File
@@ -707,12 +707,9 @@ jobs:
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Ubuntu release - name: Deploy Ubuntu release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }} if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
uses: rickstaa/action-create-tag@v1 run: |
with: git -c user.name="${GITHUB_ACTOR}" -c user.email="${GITHUB_ACTOR}@users.noreply.github.com" tag -f -a nightly-builds "${GITHUB_SHA}" -m nightly-builds
tag: "nightly-builds" git push -f origin refs/tags/nightly-builds
tag_exists_error: false
force_push_tag: true
message: "nightly-builds"
- name: Deploy Ubuntu OrcaSlicer_profile_validator release - name: Deploy Ubuntu OrcaSlicer_profile_validator release
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }} if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
+2 -2
View File
@@ -156,7 +156,7 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0 libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
- uses: actions/setup-python@v6 - uses: actions/setup-python@v6
with: with:
@@ -224,7 +224,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
xvfb xdotool imagemagick openbox mesa-utils \ xvfb xdotool imagemagick openbox mesa-utils \
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0 libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
- name: Run the parity harness - name: Run the parity harness
run: | run: |
-359
View File
@@ -1,359 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
# PrusaSlicer specifics:
# This file is backported from CMake 3.15 distribution to behave uniformly
# across all versions of CMake. It explicitly adds GLEW_STATIC compile
# definition to static targets which is needed to prevent link errors.
#[=======================================================================[.rst:
FindGLEW
--------
Find the OpenGL Extension Wrangler Library (GLEW)
Input Variables
^^^^^^^^^^^^^^^
The following variables may be set to influence this module’s behavior:
``GLEW_USE_STATIC_LIBS``
to find and create :prop_tgt:`IMPORTED` target for static linkage.
``GLEW_VERBOSE``
to output a detailed log of this module.
Imported Targets
^^^^^^^^^^^^^^^^
This module defines the following :ref:`Imported Targets <Imported Targets>`:
``GLEW::glew``
The GLEW shared library.
``GLEW::glew_s``
The GLEW static library, if ``GLEW_USE_STATIC_LIBS`` is set to ``TRUE``.
``GLEW::GLEW``
Duplicates either ``GLEW::glew`` or ``GLEW::glew_s`` based on availability.
Result Variables
^^^^^^^^^^^^^^^^
This module defines the following variables:
``GLEW_INCLUDE_DIRS``
include directories for GLEW
``GLEW_LIBRARIES``
libraries to link against GLEW
``GLEW_SHARED_LIBRARIES``
libraries to link against shared GLEW
``GLEW_STATIC_LIBRARIES``
libraries to link against static GLEW
``GLEW_FOUND``
true if GLEW has been found and can be used
``GLEW_VERSION``
GLEW version
``GLEW_VERSION_MAJOR``
GLEW major version
``GLEW_VERSION_MINOR``
GLEW minor version
``GLEW_VERSION_MICRO``
GLEW micro version
#]=======================================================================]
include(FindPackageHandleStandardArgs)
if(APPLE)
find_package(OpenGL QUIET)
if(OpenGL_FOUND)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Found OpenGL Framework.")
message(STATUS "FindGLEW: OPENGL_LIBRARIES: ${OPENGL_LIBRARIES}")
endif()
else()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: could not find GLEW library.")
endif()
return()
endif()
endif()
function(__glew_set_find_library_suffix shared_or_static)
if((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so")
elseif((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
elseif(APPLE AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib;.so")
elseif(APPLE AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
elseif(WIN32 AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib")
elseif(WIN32 AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib;.a;.dll.a")
endif()
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}" PARENT_SCOPE)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: CMAKE_FIND_LIBRARY_SUFFIXES for ${shared_or_static}: ${CMAKE_FIND_LIBRARY_SUFFIXES}")
endif()
endfunction()
if(GLEW_VERBOSE)
if(DEFINED GLEW_USE_STATIC_LIBS)
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS: ${GLEW_USE_STATIC_LIBS}.")
else()
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS is undefined. Treated as FALSE.")
endif()
endif()
find_path(GLEW_INCLUDE_DIR GL/glew.h)
mark_as_advanced(GLEW_INCLUDE_DIR)
set(GLEW_INCLUDE_DIRS ${GLEW_INCLUDE_DIR})
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_INCLUDE_DIR: ${GLEW_INCLUDE_DIR}")
message(STATUS "FindGLEW: GLEW_INCLUDE_DIRS: ${GLEW_INCLUDE_DIRS}")
endif()
if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64" OR "${CMAKE_GENERATOR}" MATCHES "Win64")
set(_arch "x64")
elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "ARM64")
set(_arch "x64") # GLEW ships one header set; ARM64 uses the x64 import path
else()
set(_arch "Win32")
endif()
set(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
__glew_set_find_library_suffix(SHARED)
find_library(GLEW_SHARED_LIBRARY_RELEASE
NAMES GLEW glew glew32
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
PATHS ENV GLEW_ROOT)
find_library(GLEW_SHARED_LIBRARY_DEBUG
NAMES GLEWd glewd glew32d
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
__glew_set_find_library_suffix(STATIC)
find_library(GLEW_STATIC_LIBRARY_RELEASE
NAMES GLEW glew glew32s
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
PATHS ENV GLEW_ROOT)
find_library(GLEW_STATIC_LIBRARY_DEBUG
NAMES GLEWds GLEWd glewd glewds glew32ds
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES})
unset(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES)
include(SelectLibraryConfigurations)
select_library_configurations(GLEW_SHARED)
select_library_configurations(GLEW_STATIC)
if(NOT GLEW_USE_STATIC_LIBS)
set(GLEW_LIBRARIES ${GLEW_SHARED_LIBRARY})
else()
set(GLEW_LIBRARIES ${GLEW_STATIC_LIBRARY})
endif()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_RELEASE: ${GLEW_SHARED_LIBRARY_RELEASE}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_RELEASE: ${GLEW_STATIC_LIBRARY_RELEASE}")
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_DEBUG: ${GLEW_SHARED_LIBRARY_DEBUG}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_DEBUG: ${GLEW_STATIC_LIBRARY_DEBUG}")
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY: ${GLEW_SHARED_LIBRARY}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY: ${GLEW_STATIC_LIBRARY}")
message(STATUS "FindGLEW: GLEW_LIBRARIES: ${GLEW_LIBRARIES}")
endif()
# Read version from GL/glew.h file
if(EXISTS "${GLEW_INCLUDE_DIR}/GL/glew.h")
file(STRINGS "${GLEW_INCLUDE_DIR}/GL/glew.h" _contents REGEX "^VERSION_.+ [0-9]+")
if(_contents)
string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MAJOR "${_contents}")
string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MINOR "${_contents}")
string(REGEX REPLACE ".*VERSION_MICRO[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MICRO "${_contents}")
set(GLEW_VERSION "${GLEW_VERSION_MAJOR}.${GLEW_VERSION_MINOR}.${GLEW_VERSION_MICRO}")
endif()
endif()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_VERSION_MAJOR: ${GLEW_VERSION_MAJOR}")
message(STATUS "FindGLEW: GLEW_VERSION_MINOR: ${GLEW_VERSION_MINOR}")
message(STATUS "FindGLEW: GLEW_VERSION_MICRO: ${GLEW_VERSION_MICRO}")
message(STATUS "FindGLEW: GLEW_VERSION: ${GLEW_VERSION}")
endif()
find_package_handle_standard_args(GLEW
REQUIRED_VARS GLEW_INCLUDE_DIRS GLEW_LIBRARIES
VERSION_VAR GLEW_VERSION)
if(NOT GLEW_FOUND)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: could not find GLEW library.")
endif()
return()
endif()
if(NOT TARGET GLEW::glew AND NOT GLEW_USE_STATIC_LIBS)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::glew imported target.")
endif()
add_library(GLEW::glew UNKNOWN IMPORTED)
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(GLEW_SHARED_LIBRARY_RELEASE)
set_property(TARGET GLEW::glew
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::glew
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
endif()
if(GLEW_SHARED_LIBRARY_DEBUG)
set_property(TARGET GLEW::glew
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::glew
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
endif()
elseif(NOT TARGET GLEW::glew_s AND GLEW_USE_STATIC_LIBS)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::glew_s imported target.")
endif()
add_library(GLEW::glew_s UNKNOWN IMPORTED)
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
set_target_properties(GLEW::glew_s PROPERTIES INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(GLEW_STATIC_LIBRARY_RELEASE)
set_property(TARGET GLEW::glew_s
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::glew_s
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}")
endif()
if(GLEW_STATIC_LIBRARY_DEBUG)
set_property(TARGET GLEW::glew_s
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::glew_s
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}")
endif()
endif()
if(NOT TARGET GLEW::GLEW)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::GLEW imported target.")
endif()
add_library(GLEW::GLEW UNKNOWN IMPORTED)
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(TARGET GLEW::glew)
if(GLEW_SHARED_LIBRARY_RELEASE)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
endif()
if(GLEW_SHARED_LIBRARY_DEBUG)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
endif()
elseif(TARGET GLEW::glew_s)
if(GLEW_STATIC_LIBRARY_RELEASE)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}"
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
endif()
if(GLEW_STATIC_LIBRARY_DEBUG AND GLEW_USE_STATIC_LIBS)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}"
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
endif()
elseif(GLEW_VERBOSE)
message(WARNING "FindGLEW: no `GLEW::glew` or `GLEW::glew_s` target was created. Something went wrong in FindGLEW target creation.")
endif()
endif()
-3
View File
@@ -379,10 +379,8 @@ include(Boost/Boost.cmake)
include(Cereal/Cereal.cmake) include(Cereal/Cereal.cmake)
include(Qhull/Qhull.cmake) include(Qhull/Qhull.cmake)
include(GLEW/GLEW.cmake)
include(GLFW/GLFW.cmake) include(GLFW/GLFW.cmake)
include(OpenCSG/OpenCSG.cmake)
set(SLVS_PKG "") set(SLVS_PKG "")
if (SLIC3R_CAD) if (SLIC3R_CAD)
include(SLVS/SLVS.cmake) include(SLVS/SLVS.cmake)
@@ -478,7 +476,6 @@ set(_dep_list
dep_Draco dep_Draco
dep_NLopt dep_NLopt
dep_OpenVDB dep_OpenVDB
dep_OpenCSG
${SLVS_PKG} ${SLVS_PKG}
dep_OpenCV dep_OpenCV
dep_Eigen dep_Eigen
-2
View File
@@ -1,6 +1,4 @@
orcaslicer_add_cmake_project(EXPAT orcaslicer_add_cmake_project(EXPAT
# GIT_REPOSITORY https://github.com/nigels-com/glew.git
# GIT_TAG 3a8eff7 # 2.1.0
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/expat SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/expat
) )
-14
View File
@@ -1,14 +0,0 @@
# We have to check for OpenGL to compile GLEW
set(OpenGL_GL_PREFERENCE "LEGACY") # to prevent a nasty warning by cmake
find_package(OpenGL QUIET REQUIRED)
orcaslicer_add_cmake_project(
GLEW
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew
CMAKE_ARGS
-DGLEW_USE_EGL=OFF
)
if (MSVC)
add_debug_dep(dep_GLEW)
endif ()
-44
View File
@@ -1,44 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(GLEW)
find_package(OpenGL REQUIRED)
# Allow parent project to control EGL usage.
# Default to OFF since OrcaSlicer forces GDK_BACKEND=x11 (using GLX contexts).
# GLEW must use glXGetProcAddressARB (GLX) to match wxWidgets GL canvas.
# Using EGL function loading with GLX contexts causes rendering failures.
option(GLEW_USE_EGL "Use EGL instead of GLX for OpenGL function loading" OFF)
if(GLEW_USE_EGL)
message(STATUS "Building GLEW with EGL support")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL")
else()
message(STATUS "Building GLEW with GLX support")
endif()
add_library(GLEW src/glew.c)
target_include_directories(GLEW PRIVATE include/)
target_link_libraries(GLEW PUBLIC OpenGL::GL)
if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(GLEW PUBLIC GLEW_STATIC)
endif ()
include(GNUInstallDirs)
install(
FILES
${PROJECT_SOURCE_DIR}/include/GL/glew.h
${PROJECT_SOURCE_DIR}/include/GL/wglew.h
${PROJECT_SOURCE_DIR}/include/GL/glxew.h
${PROJECT_SOURCE_DIR}/include/GL/eglew.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/GL
)
install(TARGETS GLEW GLEW
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
-73
View File
@@ -1,73 +0,0 @@
The OpenGL Extension Wrangler Library
Copyright (C) 2002-2007, Milan Ikits <milan ikits[]ieee org>
Copyright (C) 2002-2007, Marcelo E. Magallon <mmagallo[]debian org>
Copyright (C) 2002, Lev Povalahev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name of the author may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
Mesa 3-D graphics library
Version: 7.0
Copyright (C) 1999-2007 Brian Paul All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copyright (c) 2007 The Khronos Group Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and/or associated documentation files (the
"Materials"), to deal in the Materials without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Materials, and to
permit persons to whom the Materials are furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Materials.
THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-251
View File
@@ -1,251 +0,0 @@
# GLEW - The OpenGL Extension Wrangler Library
The OpenGL Extension Wrangler Library (GLEW) is a cross-platform open-source C/C++ extension loading library. GLEW provides efficient run-time mechanisms for determining which OpenGL extensions are supported on the target platform. OpenGL core and extension functionality is exposed in a single header file. GLEW has been tested on a variety of operating systems, including Windows, Linux, Mac OS X, FreeBSD, Irix, and Solaris.
![](http://glew.sourceforge.net/glew.png)
http://glew.sourceforge.net/
https://github.com/nigels-com/glew
[![Build Status](https://travis-ci.org/nigels-com/glew.svg?branch=master)](https://travis-ci.org/nigels-com/glew)
[![Gitter](https://badges.gitter.im/nigels-com/glew.svg)](https://gitter.im/nigels-com/glew?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Download](https://img.shields.io/sourceforge/dm/glew.svg)](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&auml;rkk&auml;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).
-1
View File
@@ -1 +0,0 @@
2.2.0
-3051
View File
File diff suppressed because it is too large Load Diff
-26427
View File
File diff suppressed because it is too large Load Diff
-1831
View File
File diff suppressed because it is too large Load Diff
-1468
View File
File diff suppressed because it is too large Load Diff
-31949
View File
File diff suppressed because it is too large Load Diff
-101
View File
@@ -1,101 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(OpenCSG)
if (NOT BUILD_SHARED_LIBS)
set(GLEW_USE_STATIC_LIBS ON)
elseif (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
find_package(OpenGL REQUIRED)
set(GLEW_VERBOSE ON)
find_package(GLEW 1.13.0 REQUIRED)
set(_srcfiles
src/area.cpp
src/batch.cpp
src/context.cpp
src/channelManager.cpp
src/frameBufferObject.cpp
src/frameBufferObjectExt.cpp
src/occlusionQuery.cpp
src/opencsgRender.cpp
src/openglHelper.cpp
src/pBufferTexture.cpp
src/primitive.cpp
src/primitiveHelper.cpp
src/renderGoldfeather.cpp
src/renderSCS.cpp
src/scissorMemo.cpp
src/settings.cpp
src/stencilManager.cpp
RenderTexture/RenderTexture.cpp
include/opencsg.h
src/opencsgConfig.h
src/area.h
src/batch.h
src/context.h
src/channelManager.h
src/frameBufferObject.h
src/frameBufferObjectExt.h
src/occlusionQuery.h
src/offscreenBuffer.h
src/opencsgRender.h
src/openglHelper.h
src/pBufferTexture.h
src/primitiveHelper.h
src/scissorMemo.h
src/settings.h
src/stencilManager.h
)
add_library(opencsg ${_srcfiles})
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>)
target_link_libraries(opencsg PRIVATE GLEW::GLEW OpenGL::GL)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION 1.4.2
COMPATIBILITY AnyNewerVersion
)
install(TARGETS opencsg
EXPORT ${PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
export(EXPORT ${PROJECT_NAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
NAMESPACE ${PROJECT_NAME}:: )
set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Targets
FILE
"${PROJECT_NAME}Config.cmake"
NAMESPACE
${PROJECT_NAME}::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
${PROJECT_SOURCE_DIR}/include/opencsg.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/opencsg
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
)
-17
View File
@@ -1,17 +0,0 @@
orcaslicer_add_cmake_project(OpenCSG
# GIT_REPOSITORY https://github.com/floriankirsch/OpenCSG.git
# GIT_TAG 83e274457b46c9ad11a4ee599203250b1618f3b9 #v1.4.2
URL https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
URL_HASH SHA256=51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in ./CMakeLists.txt
DEPENDS dep_GLEW
)
if (TARGET ${ZLIB_PKG})
add_dependencies(dep_OpenCSG ${ZLIB_PKG})
endif()
if (MSVC)
add_debug_dep(dep_OpenCSG)
endif ()
+29
View File
@@ -44,6 +44,18 @@ else()
if(APPLE) if(APPLE)
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}) set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
else() else()
# A static library that is embedded into a shared object must not export
# its symbols. On Linux the running process also loads the system OpenSSL
# 3.x (WebKitGTK/gnutls pull in libcrypto.so.3), and CPython's _ssl and
# _hashlib are dlopened (RTLD_LOCAL) DSOs that each embed this OpenSSL.
# With default visibility their unversioned OpenSSL references are
# preempted by that global 3.x copy, mixing the 1.1.1 and 3.x ABIs and
# corrupting the heap (ssl.create_default_context() aborts). Hidden
# visibility makes each embedded copy self-contained. Linux-only: macOS
# binds dylibs with a two-level namespace (no interposition) and ships no
# OpenSSL, and Windows has no equivalent flag and no system OpenSSL to
# collide with.
set(_openssl_extra_cflags -fvisibility=hidden)
set(_conf_cmd env "CC=${CMAKE_C_COMPILER}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" "./config") set(_conf_cmd env "CC=${CMAKE_C_COMPILER}" "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}" "./config")
endif() endif()
set(_cross_comp_prefix_line "") set(_cross_comp_prefix_line "")
@@ -102,3 +114,20 @@ ExternalProject_Add_Step(dep_OpenSSL install_cmake_files
COMMAND ${CMAKE_COMMAND} -E copy_directory openssl "${DESTDIR}${CMAKE_INSTALL_LIBDIR}/cmake/openssl" COMMAND ${CMAKE_COMMAND} -E copy_directory openssl "${DESTDIR}${CMAKE_INSTALL_LIBDIR}/cmake/openssl"
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
) )
if (NOT WIN32 AND NOT APPLE)
# OpenSSL's object rules do not depend on CFLAGS, so reconfiguring it (for
# example to add -fvisibility=hidden) relinks the archives from stale
# objects instead of recompiling them, and the change silently has no
# effect. Drop the objects whenever this recipe changes so the next build
# actually recompiles them.
ExternalProject_Get_Property(dep_OpenSSL SOURCE_DIR)
ExternalProject_Add_Step(dep_OpenSSL clean_objects
DEPENDEES configure
DEPENDERS build
COMMAND make clean
WORKING_DIRECTORY "${SOURCE_DIR}"
DEPENDS "${CMAKE_CURRENT_LIST_FILE}"
COMMENT "OpenSSL: cleaning objects after a recipe change"
)
endif ()
+24
View File
@@ -299,3 +299,27 @@ endif()
if(TARGET dep_ZLIB) if(TARGET dep_ZLIB)
add_dependencies(dep_python3 dep_ZLIB) add_dependencies(dep_python3 dep_ZLIB)
endif() endif()
if (NOT WIN32 AND NOT APPLE)
# CPython's Makefile rules for _ssl and _hashlib depend only on their own
# sources, not on the OpenSSL archives, so a rebuilt OpenSSL does not make
# them relink and they keep the previous symbols. On an incremental tree,
# drop the built modules and relink them against the current OpenSSL; a
# fresh build is left alone (its PGO target builds them). "make" alone is a
# no-op once PGO has run, so sharedmods is invoked explicitly.
ExternalProject_Get_Property(dep_python3 SOURCE_DIR)
file(GLOB _python_ssl_modules
"${SOURCE_DIR}/Modules/_ssl*.so"
"${SOURCE_DIR}/Modules/_hashlib*.so")
if (_python_ssl_modules)
ExternalProject_Add_Step(dep_python3 relink_ssl_extensions
DEPENDEES configure
DEPENDERS build
COMMAND sh -c "rm -f '${SOURCE_DIR}'/Modules/_ssl*.so '${SOURCE_DIR}'/Modules/_hashlib*.so && make -j${NPROC} sharedmods"
WORKING_DIRECTORY "${SOURCE_DIR}"
COMMENT "CPython: relinking _ssl/_hashlib against the current OpenSSL"
DEPENDS "${CMAKE_CURRENT_LIST_FILE}"
"${CMAKE_CURRENT_LIST_DIR}/../OpenSSL/OpenSSL.cmake"
)
endif ()
endif ()
+1 -1
View File
@@ -191,7 +191,7 @@ public:
tail->next = std::move(p); tail->next = std::move(p);
tail = new_tail; tail = new_tail;
} }
data_cond.notify_one(); disrupt_wait_for_data();
} }
void wait_and_pop(T& value) void wait_and_pop(T& value)
+221
View File
@@ -0,0 +1,221 @@
# Adaptive TPMS infill — High Level Design
## Purpose and scope
`tpms_adaptive` grades the sparse infill of the Gyroid, TPMS-D and TPMS-FK
patterns inside the object: the cells grow continuously from the surface
towards the center of the object. `distance_warp`, `smooth_blend` and
`stepped_shells` follow the distance to the nearest surface, including the top
and bottom, like concentric shells; `lobes` follows the whole 3D shape towards
the center of each lobe of the object; `normal_z`, `normal_y` and `normal_x`
follow each section of the object normal to that axis, so the grading does not
change along the axis, as suits a profile extruded along it.
`sparse_infill_density` is the density at the surface, `tpms_interior_density`
the density at the center, and `tpms_adaptive_gradient` picks how the density
goes from one to the other. Only internal sparse infill is graded; the Gyroid
Z-buckling optimization does not apply to it.
The design has two parts: a field built once per object, and a pattern made
from it, warped around the center of each lobe of a body so that its cell size
follows the field, or, in the modes following the distance to the surface,
split into shells or blended between densities.
## Radial field
`TpmsRadialField` gives every point of an object the center of its lobe and a
radial coordinate: 0 at the center, 1 at the surface along the ray from the
center. `PrintObject::prepare_tpms_radial_fields()` builds it in
`bridge_over_infill()`, next to the adaptive cubic octree, because the anchoring
infill generated there has to match the printed infill. A field is built for
every mode a region uses, and is shared by the regions using that mode: the
field depends on the geometry only, the densities are applied per region in the
fill. An object thinner than the grid cells has no body in the field; no field
is kept then, and the infill falls back to the regular pattern. In the modes
following the distance to the surface, the field also gives the depth of every
point (see below).
A regular 3D grid of cubic cells is rasterized from the `lslices` of the layers,
so the field follows what is printed: negative volumes, the union of
overlapping parts and holes are taken into account, and the mesh does not need
to be closed. A padding node around the grid is always outside. The grid is
capped at about a million nodes, with cells no smaller than 0.5 mm.
- Bodies are the connected inside nodes. Each is graded on its own, so separate
parts of one object each get their own sparse center.
- A body is split into lobes around the local maxima of the depth, by an exact
Euclidean distance transform (Felzenszwalb and Huttenlocher, one pass per
axis). Two maxima are in separate lobes when the depth along the segment
between them drops below 0.8 of the shallower one, like at the neck between
two united spheres; maxima shallower than 0.3 of the deepest one are ignored.
A maximum joins the first lobe whose first maximum it sees without a neck.
The lobes are made one at a time, the remaining maxima tested against the
first one in parallel, as a plate has a whole plane of them.
Where the depth ties along a line or a plane, as in a tall box, the lobe's
center is the node nearest to the middle of the tied nodes, so the center is
in the middle of the height and not a column.
- A point belongs to the lobe it is nearest to relative to their depths, so the
side between two lobes is nearer to the smaller one. Near that side, within a
tenth of that relative distance, the patterns of the lobes morph into each
other, so the lines stay continuous. Every lobe in that range takes part, up
to four, so the morph is also continuous where three or four lobes meet.
- The reach of a lobe is the distance from its center to the first exit along
24 x 48 latitude-longitude directions, smoothed twice over neighbouring
directions in log space. Towards a neighbouring lobe it stops at twice the
distance to the side between them, so that side is graded half way, as deep
as a neck is, rather than as sparse as the center or as dense as the surface.
Only the lobes whose centers are near enough to be nearer at the current
distance are compared along a ray, so many lobes, as in a perforated plate,
stay cheap.
The radial coordinate of a point is its distance to the center over the reach
in its direction. Behind a gap, as across the hole
of a ring, the radial coordinate is above 1 and the infill keeps the surface
density.
- Every outside node belongs to its nearest body, so points near a surface find
their body without a search. With a single body, all nodes belong to it.
In the 2D modes, every plane of nodes normal to the axis is a field of its own:
the distance transform skips the axis, bodies, lobes and the nearest body are
found within the plane, and the reach is sampled on a circle of 48 directions.
A point is looked up in the two planes around it, the weights of their lobes
interpolated along the axis, so the grading does not step between planes; a
plane without a body uses the nearest one that has one. The planes are a cell
apart, not a layer: where the sections change abruptly, as at a step, the
patterns of the two planes morph into each other over that cell.
A distance to the nearest surface would be the obvious field, but no smooth map
follows it. By the divergence theorem, the mean scale of a map over a body is
fixed by its values on the surface: a map that keeps the full density along the
whole surface, as the distance would ask under the top and bottom, has the mean
density of the uniform infill, the sparser core being paid for by lines crowding
along the walls. The layers of a plate at different depths would also need
different line spacings in the same directions, which no continuous map allows
without shearing across the plate. Following the distance needs changes of the
topology of the lattice (see below). The radial coordinate instead grades what a
single map can: towards one point.
## Warped pattern
The pattern is evaluated on warped coordinates:
TPMS(f_surface * m(t) * (p - center))
where `m` scales the pattern around the center of the lobe: its frequency is
`m + t * m'` along the ray and `m` across it. `m(t)` is the mean of the target
scale over the ball of radius `t`, `3 / t^3 * integral of s^2 * target(s) ds`, so
the mean of the three, and with it the density, follows the gradient. The cells
are round at the center; near the surface they are flattened, with the lines
running parallel to it. Beyond the surface the target is the surface scale, so
the warp extends continuously outside.
In the 2D modes only the coordinates within the plane are warped, and `m(t)` is
the mean over the disc, `2 / t^2 * integral of s * target(s) ds`. Along the axis
the pattern keeps the interior frequency: scaling it with `m` would shear the
pattern by the distance along the axis times the gradient of `m`, without bound
on a long object. The cells are round at the center and stretched along the
axis near the surface. With Normal Z the layers are graded exactly, since
the lines of a layer follow its in-plane frequencies; normal to X or Y, the
layers near the sides are as dense as the larger of the two frequencies in the
layer, which is the surface one.
Evaluating a TPMS at a frequency that varies with the position without such a
map distorts it wherever the frequency changes, because the phase also changes
with the gradient of the frequency times the distance from the origin. Fitting a
smooth map to a varying isotropic scale in the least-squares sense (a Poisson
problem per axis) cannot grade strongly: its divergence is the target scale plus
a harmonic function pinned by the surface, which keeps the scale in the core
near two thirds of the surface one. Following a distance exactly needs the
lattice to change its topology, by blending lattices of different densities or
filling shells of equal distance with them, as the modes following the distance
to the surface do.
The target scale at depth `d = 1 - t`, with `S` the surface and `I` the interior
frequency, both from each pattern's own density calibration:
| Gradient | Scale |
|-------------|------------------------|
| Linear | `1 + (I / S - 1) * d` |
| Quadratic | `1 + (I / S - 1) * d^2`|
| Exponential | `(I / S)^d` |
With a denser surface, quadratic keeps the surface density deepest and
exponential drops fastest. A denser interior works the same way.
The zero level is extracted with marching squares like the regular TPMS-FK, on
a sampling grid fixed in the fill frame like the optimized Gyroid, so that every
region of a layer connects its lines the same way at the saddles of the pattern.
Loops narrower than two lines (shorter than `2 * PI * spacing`) are dropped, as
they would print as blobs. The fill works in a frame rotated by the infill
angle, so the radial field is looked up at the point rotated back into the
object frame, and the center rotated into the fill frame. Both use the middle of
the layer.
## Modes following the distance to the surface
`distance_warp`, `smooth_blend` and `stepped_shells` grade by the distance to
the nearest surface, including the top and bottom, as concentric shells do. The
depth of a point is that distance over the distance of the deepest point of its
body, from 0 at the surface to 1; the field keeps it for every node and
interpolates it between them. A tall box so keeps its whole axis as sparse as
its center, and a plate is graded through its thickness.
No single smooth pattern follows that depth without distortion (see above), so
the three modes trade differently:
- Distance warp keeps the lobes and the warp of Lobes, but its radial profile
comes from the depth. For every direction of a lobe, the mean depth over the
ball along the ray is sampled at 33 radii up to the reach, then smoothed over
the neighbouring directions like the reach. The radial coordinate is the one
of the linear profile with the same mean depth, `t = 4/3 * (1 - mean depth)`,
so with a linear gradient the mean cell size follows the depth exactly, and
with the others approximately. In a sphere or a cube, where the depth falls
linearly along every ray, it is Lobes. Elsewhere the profile changes with the
direction, and the warp shears where neighbouring directions differ, as in
plates and long bodies; right under the top of a long body the cells are
sparser within the layer, the warp moving their density into the height.
The profiles are smoothed over the directions like the reach, as sharper
ones shear the pattern across the layer, which adds lines. A long body is so
graded partly along its length, between Lobes and the distance.
- Smooth blend evaluates the regular patterns of the two levels around the
target of every point and blends them by a smoothstep over the whole gap
between the levels, here at most 2.5 times apart. The density follows the
depth without steps, but where two lattices blend, part of their lines run
along the blend, so fewer levels print fewer extra lines. From 25% to 5%,
three levels print about 0.45 of the uniform infill in a deep core whose
levels alone would print 0.3; levels 1.5 times apart print 0.6 to 0.7, and a
single blend from the surface to the interior 0.6.
- Stepped shells split each region of a layer into shells and fill every shell
with the regular pattern at its density. The densities are levels from the
surface to the interior density at most 1.5 times apart, five from 25% to 5%,
and a point takes the level nearest to its target on that geometric scale.
The shells are traced by marching squares of the continuous level over the
layer on a 0.5 mm grid fixed in the object, so every region of a layer gets
the same shells, and clipped to the region. Each shell is shrunk by half a
line, like a filled region, and its regular filler connects its lines along
that boundary, so the connections of two neighbouring shells lie side by side
instead of on top of each other. The pattern is never
distorted, but its lines end at every shell, and thin parts get thin shells.
The connections add lines: in the core of a 60 mm cube, about a third more
than the target.
Stepped shells and Smooth blend need neither lobes nor reaches, which are not
built for them.
## Constraints
- With `tpms_adaptive` disabled, or for other patterns, the fill parameters are reset
to their defaults, so they neither change the infill nor split fill batches.
- `Layer::get_sparse_infill_max_void_area()` uses the sparser of the two
densities, as the voids at the center are that large.
- At a sparse infill density of 100% the sparse infill is turned into solid
infill, so there is nothing to grade: the options are hidden and no field is
built.
- The adaptive options invalidate `posPrepareInfill`, which rebuilds the field
and the anchoring infill.
- An elongated body without a neck has one center, so its far ends are graded
as the outer part of the body, and the warp shears where the reach changes
quickly with the direction. A concave body, like an L, may be split into lobes
where its maxima cannot see each other in a straight line.
- Across a ray, the scale is the mean of the gradient from the center, so the
layers right under the top and above the bottom are sparser than the surface
density in their middle, and a plate is graded from its middle outwards rather
than through its thickness.
+172
View File
@@ -0,0 +1,172 @@
# Center of mass markers — High Level Design
## Purpose and scope
The "Center of mass" item of the canvas toolbar menu, in the bottom left corner of the 3D view, marks
where the mass of the plate, of each object instance and of each body of an assembly is centered,
in Prepare and in Preview. It helps judge how parts will rest on the plate, for instance whether a
tall or leaning part could tip. It is a view setting: it changes nothing in the model, the slice or
the project file, and it does not reach plate thumbnails. The choice is kept in the app config as
`show_center_of_mass`, off by default. The assembly view and the Design tab have no markers.
Three kinds of marker share one shape, a sphere whose octants alternate between two colors:
- each plate, black and white, for everything on it;
- each object instance, light blue and white;
- each body of an assembly, red and yellow;
- in Preview, the supports and raft of each object instance, green and black.
A click on a marker opens a box beside it with the weight and volume of what it stands for, where its
center lies in that thing's bounding box and the size of the box, and its moments of inertia about
axes through the center parallel to x, y and z.
An assembly is an object of several parts or with negative volumes. Its bodies are the connected
solids its parts make once united, the bodies the separated infills option centers its infill on
(see separated-infills.md): parts that overlap or touch are one body, parts apart are separate
bodies. An object of one body, and every object of a single part, has no body markers, as its object
marker says it all.
Mass is volume times density, the `filament_density` of the filament that prints it, or 1.245 g/cm³
(`DEFAULT_FILAMENT_DENSITY`) for a filament without one. Prepare has the model only, so it takes each
part as a solid of the density of its filament, the part's own or else its object's. It reads each
filament's density from the filament's selected preset, edits not yet saved included, as slicing does:
the plater's own config holds the values of the filament edited last only. Preview has
what will be printed, so its markers come from the toolpaths, whose mass depends on walls, infill
and flow as well. There the solid markers are for what is printed up to the top layer the layer
slider shows: for the plate with brim, raft and supports, where the weight rests at that point of
the print; for an object or a body, its own extrusions. Each has a faded twin for the same at the
end of the print, so the slider shows the weight moving toward where it ends, and at the top layer
the two meet. In Prepare the plate has the model alone, as brim, raft and supports exist only once
sliced.
## Prepare: from the meshes
An object of one part takes the mass properties of its mesh at unit density, times its density, from
`its_mass_properties()`, which handles a mesh in a single pass. Each triangle and a fixed vertex of
the mesh span a tetrahedron whose signed volume is `V = a · (b × c) / 6`, with `a`, `b`, `c` taken
relative to that vertex. By the divergence theorem these volumes add up to the volume of a closed
mesh, their volume-weighted centroids to its center of mass, and their second moments
`V (a aᵀ + b bᵀ + c cᵀ + s sᵀ) / 20`, with `s = a + b + c`, to its own. Shells facing inward, such as
a cavity, subtract themselves, and flipping every triangle changes nothing. The sums are kept in
double precision and relative to a vertex of the mesh rather than the origin, which keeps them exact
for meshes far from it. The result keeps the spread of the mass about its center,
`(x - c)(x - c)ᵀ` averaged over the mass, from which the moments of inertia follow.
The CGAL routines that look alike do not compute this. `CGAL::centroid` weighs tetrahedra by their
unsigned volume, so it fails on cavities and on any shell that is not star-shaped from the fan's
apex; over triangles it returns the centroid of the surface, and over points the average of the
vertices, which depends on the tessellation. `CGAL::barycenter` with the signed volumes as weights
gives the same answer, but only after copying every tetrahedron into a vector of weighted points,
and takes two and a half times as long.
A mesh's result is in its own coordinates. Each `GLVolume` maps it to the world with its world
matrix `M`, and weighs it by the volume times the absolute determinant of that matrix: a center of
mass moves with any affine map, and the spread becomes `L S Lᵀ` for the linear part `L` of `M`, so no
mesh is ever transformed. The `GLVolume`'s matrices, rather than the
model's, let the markers follow an object while it is dragged, before the model is updated. Results
are cached by `ModelVolume` id; a `ModelVolume` takes a new id whenever its mesh changes, which is
the rule `reload_scene()` relies on to rebuild a `GLVolume`'s geometry, so a cached result never
outlives its mesh.
An assembly's parts overlap or touch, which the mesh formula would count twice, so `solid_bodies()`
slices them instead, in the object's coordinates. It cuts the height into 500 slabs, 100 while a part
is dragged, slices every part and negative volume at the middle of each slab, unites the parts and
cuts the negative volumes away, and links the islands of neighboring slabs that overlap into bodies
with `connected_bodies()`. Each island adds a prism of the slab's thickness at the density: its
area, and its first and second moments of area, from the same sums over the outline as the area,
with the slab's height for z. Where parts of different densities overlap, the later volume of the object counts, as
slicing clips every part by the parts after it; each part then weighs the region it prints, which is
credited to the island holding it. Each body also keeps the convex hull of its islands and the height
they span, whose corners, once transformed, give its bounding box, tight while the instance turns
about z only. The object is the sum of its bodies, its box that of its parts, as the object's size
shows it, and each plate the sum of the object instances `PartPlateList::find_instance()` puts on it,
so that an instance on no plate counts in none. The bodies are cached by `ModelObject` id with the volumes, types, densities and
transformations they were sliced from.
## Preview: from the toolpaths
`GCodeProcessor` sums the masses while it processes the G-code, in the same pass that builds the
moves, and leaves them in `GCodeProcessorResult`; `GCodeViewer` keeps a copy of them when it loads a
result. Nothing is stored per move.
Each extrusion weighs the volume of filament its E extrudes times the density of the filament that
extrudes it, so a print of several materials weighs each as it is. Flow ratio, line widths, ironing
and purging into infill all count
as printed. Its mass spreads evenly along the segment the bead's center runs, half the layer height
below the nozzle, in the frame of the stored moves: plate offset added, Z offset removed. Such a
segment from `a` to `b` adds `m (a + b) / 2` to the moments and `m (a² + a b + b²) / 3` to the second
moments along each axis, and its box widened by half the bead's height to the bounding box; not by
half its width, which the processor only estimates, so that a box runs along the walls' center lines. Arcs are already split into segments by the processor. Walls, infill, top and bottom surfaces, ironing
and gap fill make the parts. The brim and the support roles, the raft among them, count only in what
the plate prints. The skirt, the prime tower and custom G-code count nowhere.
The plate takes every extrusion, so it needs nothing more. The objects and bodies need the sliced
objects, which the G-code does not describe, so the G-code export hands the processor a locator
built from the `Print`; G-code opened from a file, or from a project sliced earlier, has no `Print`
behind it, and so shows the plate alone. Object labels would not do: profiles turn the four kinds
Orca writes on and off in every combination, and none of them tells the bodies apart.
The locator numbers the object instances and, for each assembly, the bodies of every instance. It
takes the bodies `PrintObject::prepare_infill()` found for separated infills, or, when that option
did not need them, links the islands (`Layer::lslices`) of neighboring layers into bodies with the
same `connected_bodies()`. For each extrusion of a part, it finds the first layer printed at or above
its height, as spiral vase rises through each layer, and the island holding it with an
`IslandLocator`, the one `solid_bodies()` credits its regions with: by the island's box, widened by
1 mm for walls reaching past it, with a polygon test only where boxes overlap, and the nearest
outline where none holds the point. The boxes of one layer's islands say nothing of the other
instances, so an instance whose widened box reaches another's, as copies placed side by side do,
tests the outlines alone, and outside them the nearest outline of all such instances wins. The island
gives both the instance and the body. The island found last is tried first, as extrusions mostly follow each other on one
island. Brim, raft and supports lie outside the islands. The brim counts in the plate only; a support
or raft extrusion goes to the instance whose footprint, the box of its widened islands, holds it, the
one whose center is nearest among several, or else the nearest footprint, as supports stand below and
around their object.
Each mass holds, for each layer id, the running total of what is printed up to that layer, the last
of which is the faded marker's, so the solid marker for any slider position is a single lookup. The layer ids are those
the moves carry, which are also the layers of libvgcode and of the slider; in a print by object they
follow the order of printing, so the solid markers show the objects printed so far as they are.
## Drawing
`smooth_sphere()` with a resolution divisible by four leaves every triangle within one octant, so it
splits into two models drawn with the `gouraud_light` shader in each kind's two colors. The radius is
9 pixels for the plate, 7 for the objects, 6 for the supports and 5 for the bodies, scaled like the canvas toolbar for the
display's DPI and kept constant on screen through the camera's inverse zoom. They are drawn in that
order, so that markers at one place show as rings. The faded markers are the same spheres at 40%
opacity, drawn before all the solid ones, which show over them where both meet.
The centers usually lie inside the objects, so the markers are drawn without the depth test and show
through the objects and anything in front of them. Back face culling keeps the far half of a sphere
from covering the near one. They are drawn after the ambient occlusion pass, which would otherwise
darken them as the surface behind them, and before FXAA, which smooths their edges.
The markers are part of the cached scene, so toggling them, or changing a filament's density while
they are shown, marks the scene dirty, and moving the layer slider redraws the scene with the solid
markers where they belong. In Prepare they are hidden while any gizmo other than Move, Rotate, Scale
and Lay on face is open, since the others work on the surface a marker would cover, and a hidden
object has no markers.
## Details
The markers drawn last are kept, and a left click is tested against them before it selects: each
center and a point a radius to its right are projected to the screen, and the click hits a marker
within that distance. The solid markers are tested before the faded ones and the smaller kinds
before the larger, the order in which they cover each other. A hit opens the details of that marker
and keeps the click from changing the selection; a click anywhere else closes them. The box is an
ImGui window beside the marker, redrawn with the overlay from the markers of the last scene, so it
follows a dragged object, and in Preview the layer slider. It closes when its marker is gone, or when
the number of markers of its kind changes, as then it may stand for something else.
Its title says what the marker stands for: the plate, an object, an assembly or a part, the body of
an assembly. The G-code export lists the object instances for the processor, marking assemblies, as
it hands it the locator.
Each marker carries its sums: mass, volume, first moments and the second moments about the origin
along each axis, `Σ m x²`, `Σ m y²` and `Σ m z²`, which add up from parts to objects to plates. The
moment of inertia about the axis through the center parallel to x is then
`m (σy² + σz²)`, with `σ² = Σ m x² / m - c²` along each axis, and likewise for y and z. Masses are
kept in mg, volume times density in g/cm³, and shown in g, volumes in cm³ and moments of inertia in
g·mm². In Preview the box tells the finished print from what is printed up to the layer shown, the
two weighing differently, and both are placed in the bounding box of everything the marker holds
by the end.
+2 -2
View File
@@ -18,8 +18,8 @@ each body on its own (see Octree infill).
## Bodies ## Bodies
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`) `PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
into 3D connected bodies before bridges are detected, so bridge anchors and into 3D connected bodies with `connected_bodies()` before bridges are detected,
printed infill share one origin. Islands on adjacent layers belong to one body so bridge anchors and printed infill share one origin. Islands on adjacent layers belong to one body
when their slices overlap. Parts that touch or overlap form one body. Separate when their slices overlap. Parts that touch or overlap form one body. Separate
parts, disconnected islands of one mesh, and interleaved parts that never touch, parts, disconnected islands of one mesh, and interleaved parts that never touch,
such as chain links, each form their own. Every island stores the index of its such as chain links, each form their own. Every island stores the index of its
@@ -10,6 +10,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000" "4000"
@@ -10,6 +10,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000", "4000",
@@ -9,6 +9,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000" "4000"
@@ -9,6 +9,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000" "4000"
@@ -10,6 +10,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000", "4000",
@@ -11,6 +11,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000", "4000",
@@ -11,6 +11,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000", "10000",
@@ -11,6 +11,7 @@
"30", "30",
"30" "30"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000", "10000",
@@ -11,6 +11,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"4000", "4000",
"4000", "4000",
@@ -11,6 +11,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000", "10000",
@@ -11,6 +11,7 @@
"30", "30",
"30" "30"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000", "10000",
@@ -10,6 +10,7 @@
"30", "30",
"30" "30"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000" "10000"
@@ -10,6 +10,7 @@
"50", "50",
"50" "50"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000", "10000",
@@ -11,6 +11,7 @@
"30", "30",
"30" "30"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000", "10000",
@@ -10,6 +10,7 @@
"30", "30",
"30" "30"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000" "10000"
@@ -10,6 +10,7 @@
"30", "30",
"30" "30"
], ],
"enable_arc_fitting": "0",
"default_acceleration": [ "default_acceleration": [
"10000", "10000",
"10000" "10000"
-1
View File
@@ -2,5 +2,4 @@
#add_subdirectory(openvdb) #add_subdirectory(openvdb)
# add_subdirectory(meshboolean) # add_subdirectory(meshboolean)
add_subdirectory(its_neighbor_index) add_subdirectory(its_neighbor_index)
# add_subdirectory(opencsg)
#add_subdirectory(aabb-evaluation) #add_subdirectory(aabb-evaluation)
-30
View File
@@ -1,30 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(OpenCSG-example)
add_executable(opencsg_example WIN32
main.cpp
Engine.hpp Engine.cpp
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/Jobs/Job.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
find_package(OpenCSG REQUIRED)
include(${wxWidgets_USE_FILE})
target_link_libraries(opencsg_example libslic3r)
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
OpenCSG::opencsg
GLEW::GLEW
OpenGL::GL
#-lXrandr -lXext -lX11
)
-495
View File
@@ -1,495 +0,0 @@
#include "Engine.hpp"
#include <libslic3r/Utils.hpp>
#include <libslic3r/SLAPrint.hpp>
#include <GL/glew.h>
#include <boost/log/trivial.hpp>
#ifndef NDEBUG
#define HAS_GLSAFE
#endif
#ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
{
GLenum err = glGetError();
if (err == GL_NO_ERROR)
return;
const char *sErr = 0;
switch (err) {
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
default: sErr = "Unknown"; break;
}
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
assert(false);
}
#else
inline void glAssertRecentCall() { }
#define glsafe(cmd) cmd
#define glcheck()
#endif
namespace Slic3r { namespace GL {
Scene::Scene() = default;
Scene::~Scene() = default;
void CSGDisplay::render_scene()
{
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
glsafe(::glColor4fv(color));
if (m_csgsettings.is_enabled()) {
OpenCSG::render(m_scene_cache.primitives_csg);
glDepthFunc(GL_EQUAL);
}
for (auto& p : m_scene_cache.primitives_csg) p->render();
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
for (auto& p : m_scene_cache.primitives_free) p->render();
glFlush();
}
void Scene::set_print(std::unique_ptr<SLAPrint> &&print)
{
m_print = std::move(print);
// Notify displays
call(&Listener::on_scene_updated, m_listeners, *this);
}
BoundingBoxf3 Scene::get_bounding_box() const
{
return m_print->model().bounding_box();
}
void CSGDisplay::SceneCache::clear()
{
primitives_csg.clear();
primitives_free.clear();
primitives.clear();
}
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
{
auto p = std::make_shared<Primitive>();
p->load_mesh(mesh);
primitives.emplace_back(p);
primitives_free.emplace_back(p.get());
return p;
}
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
OpenCSG::Operation o,
unsigned c)
{
auto p = std::make_shared<Primitive>(o, c);
p->load_mesh(mesh);
primitives.emplace_back(p);
primitives_csg.emplace_back(p.get());
return p;
}
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
{
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
this->vertices_and_normals_interleaved.emplace_back(nx);
this->vertices_and_normals_interleaved.emplace_back(ny);
this->vertices_and_normals_interleaved.emplace_back(nz);
this->vertices_and_normals_interleaved.emplace_back(x);
this->vertices_and_normals_interleaved.emplace_back(y);
this->vertices_and_normals_interleaved.emplace_back(z);
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
}
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
this->triangle_indices.emplace_back(idx1);
this->triangle_indices.emplace_back(idx2);
this->triangle_indices.emplace_back(idx3);
this->triangle_indices_size = this->triangle_indices.size();
}
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
{
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
assert(quad_indices.empty() && triangle_indices_size == 0);
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
int vertices_count = 0;
for (size_t i = 0; i < mesh.facets_count(); ++i) {
const stl_facet &facet = mesh.stl.facet_start[i];
for (int j = 0; j < 3; ++j)
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
vertices_count += 3;
}
}
void IndexedVertexArray::finalize_geometry()
{
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
assert(this->triangle_indices_VBO_id == 0);
assert(this->quad_indices_VBO_id == 0);
if (!this->vertices_and_normals_interleaved.empty()) {
glsafe(
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
this->vertices_and_normals_interleaved_VBO_id));
glsafe(
::glBufferData(GL_ARRAY_BUFFER,
GLsizeiptr(
this->vertices_and_normals_interleaved.size() *
4),
this->vertices_and_normals_interleaved.data(),
GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->vertices_and_normals_interleaved.clear();
}
if (!this->triangle_indices.empty()) {
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->triangle_indices_VBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
GLsizeiptr(this->triangle_indices.size() * 4),
this->triangle_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->triangle_indices.clear();
}
if (!this->quad_indices.empty()) {
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->quad_indices_VBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
GLsizeiptr(this->quad_indices.size() * 4),
this->quad_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->quad_indices.clear();
}
}
void IndexedVertexArray::release_geometry()
{
if (this->vertices_and_normals_interleaved_VBO_id) {
glsafe(
::glDeleteBuffers(1,
&this->vertices_and_normals_interleaved_VBO_id));
this->vertices_and_normals_interleaved_VBO_id = 0;
}
if (this->triangle_indices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
this->triangle_indices_VBO_id = 0;
}
if (this->quad_indices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
this->quad_indices_VBO_id = 0;
}
this->clear();
}
void IndexedVertexArray::render() const
{
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
assert(this->triangle_indices_VBO_id != 0 ||
this->quad_indices_VBO_id != 0);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
this->vertices_and_normals_interleaved_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
reinterpret_cast<const void *>(3 * sizeof(float))));
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
// Render using the Vertex Buffer Objects.
if (this->triangle_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->triangle_indices_VBO_id));
glsafe(::glDrawElements(GL_TRIANGLES,
GLsizei(this->triangle_indices_size),
GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
if (this->quad_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->quad_indices_VBO_id));
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void IndexedVertexArray::clear() {
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
vertices_and_normals_interleaved_size = 0;
triangle_indices_size = 0;
quad_indices_size = 0;
}
void IndexedVertexArray::shrink_to_fit() {
this->vertices_and_normals_interleaved.shrink_to_fit();
this->triangle_indices.shrink_to_fit();
this->quad_indices.shrink_to_fit();
}
void Volume::render()
{
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
m_geom.render();
glsafe(::glPopMatrix());
}
void Display::clear_screen()
{
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
Display::~Display()
{
OpenCSG::freeResources();
}
void Display::set_active(long width, long height)
{
if (!m_initialized) {
glewInit();
m_initialized = true;
}
// gray background
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Enable two OpenGL lights
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHTING);
glEnable(GL_NORMALIZE);
// Use depth buffering for hidden surface elimination
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
set_screen_size(width, height);
}
void Display::set_screen_size(long width, long height)
{
if (m_size.x() != width || m_size.y() != height)
m_camera->set_screen(width, height);
m_size = {width, height};
}
void Display::repaint()
{
clear_screen();
m_camera->view();
render_scene();
m_fps_counter.update();
swap_buffers();
}
void Controller::on_scene_updated(const Scene &scene)
{
const SLAPrint *print = scene.get_print();
if (!print) return;
auto bb = scene.get_bounding_box();
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
m_wheel_pos = long(2 * d);
call_cameras(&Camera::set_zoom, m_wheel_pos);
call(&Display::on_scene_updated, m_displays, scene);
}
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
{
m_wheel_pos += v / d;
call_cameras(&Camera::set_zoom, m_wheel_pos);
call(&Display::repaint, m_displays);
}
void Controller::on_moved_to(long x, long y)
{
if (m_left_btn) {
call_cameras(&Camera::rotate, (Vec2i32{x, y} - m_mouse_pos).cast<float>());
call(&Display::repaint, m_displays);
}
m_mouse_pos = {x, y};
}
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
{
using namespace OpenCSG;
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
m_csgsettings = settings;
setOption(AlgorithmSetting, m_csgsettings.get_algo());
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
if (needupdate) {
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
if (p->getConvexity() > 1)
p->setConvexity(m_csgsettings.get_convexity());
}
}
void CSGDisplay::on_scene_updated(const Scene &scene)
{
const SLAPrint *print = scene.get_print();
if (!print) return;
m_scene_cache.clear();
for (const SLAPrintObject *po : print->objects()) {
const ModelObject *mo = po->model_object();
TriangleMesh msh = mo->raw_mesh();
sla::DrainHoles holedata = mo->sla_drain_holes;
for (const ModelInstance *mi : mo->instances) {
TriangleMesh mshinst = msh;
auto interior = po->hollowed_interior_mesh();
interior.transform(po->trafo().inverse());
mshinst.merge(interior);
mi->transform_mesh(&mshinst);
auto bb = mshinst.bounding_box();
auto center = bb.center().cast<float>();
mshinst.translate(-center);
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
m_csgsettings.get_convexity());
}
for (const sla::DrainHole &holept : holedata) {
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
}
}
repaint();
}
void Camera::view()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
m_referene.x(), m_referene.y(), m_referene.z(),
0.0, 0.0, 1.0); /* up is in positive Y direction */
// TODO Could have been set in prevoius gluLookAt in first argument
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
if (m_clip_z > 0.) {
GLdouble plane[] = {0., 0., 1., m_clip_z};
glClipPlane(GL_CLIP_PLANE0, plane);
glEnable(GL_CLIP_PLANE0);
} else {
glDisable(GL_CLIP_PLANE0);
}
}
void PerspectiveCamera::set_screen(long width, long height)
{
// Setup the view of the CSG shape
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, width / double(height), .1, 200.0);
glMatrixMode(GL_MODELVIEW);
}
bool enable_multisampling(bool e)
{
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
GLint is_ms_context;
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
else return false;
}
MouseInput::Listener::~Listener() = default;
void FpsCounter::update()
{
++m_frames;
TimePoint msec = Clock::now();
double seconds_window = to_sec(msec - m_window);
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
if (to_sec(msec - m_last) >= m_resolution) {
m_last = msec;
for (auto &l : m_listeners) l(m_fps);
}
if (seconds_window >= m_window_size) {
m_frames = 0;
m_window = msec;
}
}
}} // namespace Slic3r::GL
-488
View File
@@ -1,488 +0,0 @@
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
#include <vector>
#include <memory>
#include <chrono>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <opencsg/opencsg.h>
namespace Slic3r {
class SLAPrint;
namespace GL {
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
// remove empty weak pointers from a vector
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
auto it = std::remove_if(listeners.begin(), listeners.end(),
[](auto &l) { return !l.lock(); });
listeners.erase(it, listeners.end());
}
// Call a class method on each element of a vector of objects (weak pointers)
// of the same type.
template<class F, class L, class...Args>
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
for (auto &l : listeners)
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
}
// A representation of a mouse input for the engine.
class MouseInput
{
public:
enum WheelAxis { waVertical, waHorizontal };
// Interface to implement if an object wants to receive notifications
// about mouse events.
class Listener {
public:
virtual ~Listener();
virtual void on_left_click_down() {}
virtual void on_left_click_up() {}
virtual void on_right_click_down() {}
virtual void on_right_click_up() {}
virtual void on_double_click() {}
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
virtual void on_moved_to(long /*x*/, long /*y*/) {}
};
private:
vector<std::weak_ptr<Listener>> m_listeners;
public:
virtual ~MouseInput() = default;
virtual void left_click_down()
{
call(&Listener::on_left_click_down, m_listeners);
}
virtual void left_click_up()
{
call(&Listener::on_left_click_up, m_listeners);
}
virtual void right_click_down()
{
call(&Listener::on_right_click_down, m_listeners);
}
virtual void right_click_up()
{
call(&Listener::on_right_click_up, m_listeners);
}
virtual void double_click()
{
call(&Listener::on_double_click, m_listeners);
}
virtual void scroll(long v, long d, WheelAxis wa)
{
call(&Listener::on_scroll, m_listeners, v, d, wa);
}
virtual void move_to(long x, long y)
{
call(&Listener::on_moved_to, m_listeners, x, y);
}
void add_listener(std::shared_ptr<Listener> listener)
{
m_listeners.emplace_back(listener);
cleanup(m_listeners);
}
};
// This is a stripped down version of Slic3r::IndexedVertexArray
class IndexedVertexArray {
public:
~IndexedVertexArray() { release_geometry(); }
// Vertices and their normals, interleaved to be used by void
// glInterleavedArrays(GL_N3F_V3F, 0, x)
vector<float> vertices_and_normals_interleaved;
vector<int> triangle_indices;
vector<int> quad_indices;
// When the geometry data is loaded into the graphics card as Vertex
// Buffer Objects, the above mentioned std::vectors are cleared and the
// following variables keep their original length.
size_t vertices_and_normals_interleaved_size{ 0 };
size_t triangle_indices_size{ 0 };
size_t quad_indices_size{ 0 };
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
unsigned int triangle_indices_VBO_id{ 0 };
unsigned int quad_indices_VBO_id{ 0 };
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
inline void push_geometry(
double x, double y, double z, double nx, double ny, double nz)
{
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
inline void push_geometry(const Vec3d &p, const Vec3d &n)
{
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
}
void push_triangle(int idx1, int idx2, int idx3);
void load_mesh(const TriangleMesh &mesh);
inline bool has_VBOs() const
{
return vertices_and_normals_interleaved_VBO_id != 0;
}
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been
// loaded into the VBOs.
void finalize_geometry();
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
void render() const;
// Is there any geometry data stored?
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
void clear();
// Shrink the internal storage to tighly fit the data stored.
void shrink_to_fit();
};
// Try to enable or disable multisampling.
bool enable_multisampling(bool e = true);
class Volume {
IndexedVertexArray m_geom;
Geometry::Transformation m_trafo;
public:
void render();
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
void scale(double s) { scale({s, s, s}); }
inline void load_mesh(const TriangleMesh &mesh)
{
m_geom.load_mesh(mesh);
m_geom.finalize_geometry();
}
};
// A primitive that can be used with OpenCSG rendering algorithms.
// Does a similar job to GLVolume.
class Primitive : public Volume, public OpenCSG::Primitive
{
public:
using OpenCSG::Primitive::Primitive;
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
void render() override { Volume::render(); }
};
// A simple representation of a camera in a 3D scene
class Camera {
protected:
Vec2f m_rot = {0., 0.};
Vec3d m_referene = {0., 0., 0.};
double m_zoom = 0.;
double m_clip_z = 0.;
public:
virtual ~Camera() = default;
virtual void view();
virtual void set_screen(long width, long height) = 0;
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
void rotate(const Vec2f &rotation) { m_rot += rotation; }
void set_zoom(double z) { m_zoom = z; }
void set_reference_point(const Vec3d &p) { m_referene = p; }
void set_clip_z(double z) { m_clip_z = z; }
};
// Reset a camera object
inline void reset(Camera &cam)
{
cam.set_rotation({0., 0.});
cam.set_zoom(0.);
cam.set_reference_point({0., 0., 0.});
cam.set_clip_z(0.);
}
// Specialization of a camera which shows in perspective projection
class PerspectiveCamera: public Camera {
public:
void set_screen(long width, long height) override;
};
// A simple counter of FPS. Subscribed objects will receive updates of the
// current fps.
class FpsCounter {
vector<std::function<void(double)>> m_listeners;
using Clock = std::chrono::high_resolution_clock;
using Duration = Clock::duration;
using TimePoint = Clock::time_point;
int m_frames = 0;
TimePoint m_last = Clock::now(), m_window = m_last;
double m_resolution = 0.1, m_window_size = 1.0;
double m_fps = 0.;
static double to_sec(Duration d)
{
return d.count() * double(Duration::period::num) / Duration::period::den;
}
public:
void update();
void add_listener(std::function<void(double)> lst)
{
m_listeners.emplace_back(lst);
}
void clear_listeners() { m_listeners = {}; }
void set_notification_interval(double seconds);
void set_measure_window_size(double seconds);
double get_notification_interval() const { return m_resolution; }
double get_mesure_window_size() const { return m_window_size; }
};
// Collection of the used OpenCSG library settings.
class CSGSettings {
public:
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
private:
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
bool m_enable = true;
unsigned int m_convexity = DEFAULT_CONVEXITY;
public:
int get_algo() const { return int(m_csgalg); }
void set_algo(int alg)
{
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
m_csgalg = OpenCSG::Algorithm(alg);
}
int get_depth_algo() const { return int(m_depth_algo); }
void set_depth_algo(int alg)
{
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
}
int get_optimization() const { return int(m_optim); }
void set_optimization(int o)
{
if (o < OpenCSG::Optimization::OptimizationUnused)
m_optim = OpenCSG::Optimization(o);
}
void enable_csg(bool en = true) { m_enable = en; }
bool is_enabled() const { return m_enable; }
unsigned get_convexity() const { return m_convexity; }
void set_convexity(unsigned c) { m_convexity = c; }
};
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
class Scene
{
std::unique_ptr<SLAPrint> m_print;
public:
// Subscribers will be notified if the model is changed. This might be a
// display which will have to load the meshes and repaint itself when
// the scene data changes.
// eg. We load a new 3mf through the UI, this will notify the controller
// associated with the scene and all the displays that the controller is
// connected with.
class Listener {
public:
virtual ~Listener() = default;
virtual void on_scene_updated(const Scene &scene) = 0;
};
Scene();
~Scene();
void set_print(std::unique_ptr<SLAPrint> &&print);
const SLAPrint * get_print() const { return m_print.get(); }
BoundingBoxf3 get_bounding_box() const;
void add_listener(std::shared_ptr<Listener> listener)
{
m_listeners.emplace_back(listener);
cleanup(m_listeners);
}
private:
vector<std::weak_ptr<Listener>> m_listeners;
};
// The basic Display. This is almost just an interface but will do all the
// initialization and show the fps values. Overriding the render_scene is
// needed to show the scene content. The specific method of displaying the
// scene is up the particular implementation (OpenCSG or other screen space
// boolean algorithms)
class Display : public Scene::Listener
{
protected:
Vec2i32 m_size;
bool m_initialized = false;
std::shared_ptr<Camera> m_camera;
FpsCounter m_fps_counter;
public:
explicit Display(std::shared_ptr<Camera> camera = nullptr)
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
{}
~Display() override;
std::shared_ptr<const Camera> get_camera() const { return m_camera; }
std::shared_ptr<Camera> get_camera() { return m_camera; }
void set_camera(std::shared_ptr<Camera> cam) { m_camera = cam; }
virtual void swap_buffers() = 0;
virtual void set_active(long width, long height);
virtual void set_screen_size(long width, long height);
Vec2i32 get_screen_size() const { return m_size; }
virtual void repaint();
bool is_initialized() const { return m_initialized; }
virtual void clear_screen();
virtual void render_scene() {}
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
{
m_fps_counter = std::forward<_FpsCounter>(fpsc);
}
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
FpsCounter &get_fps_counter() { return m_fps_counter; }
};
// Special dispaly using OpenCSG for rendering the scene.
class CSGDisplay : public Display {
protected:
CSGSettings m_csgsettings;
// Cache the renderable primitives. These will be fetched when the scene
// is modified.
struct SceneCache {
vector<std::shared_ptr<Primitive>> primitives;
vector<Primitive *> primitives_free;
vector<OpenCSG::Primitive *> primitives_csg;
void clear();
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh);
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh,
OpenCSG::Operation op,
unsigned covexity);
} m_scene_cache;
public:
// Receive or apply the new settings.
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
void apply_csgsettings(const CSGSettings &settings);
void render_scene() override;
void on_scene_updated(const Scene &scene) override;
};
// The controller is a hub which dispatches mouse events to the connected
// displays. It keeps track of the mouse wheel position, the states whether
// the mouse is being held, dragged, etc... All the connected displays will
// mirror the camera movement (if there is more than one display).
class Controller : public std::enable_shared_from_this<Controller>,
public MouseInput::Listener,
public Scene::Listener
{
long m_wheel_pos = 0;
Vec2i32 m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
bool m_left_btn = false, m_right_btn = false;
std::shared_ptr<Scene> m_scene;
vector<std::weak_ptr<Display>> m_displays;
// Call a method of Camera on all the cameras of the attached displays
template<class F, class...Args>
void call_cameras(F &&f, Args&&... args) {
for (std::weak_ptr<Display> &l : m_displays)
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
(cam.get()->*f)(std::forward<Args>(args)...);
}
public:
// Set the scene that will be controlled.
void set_scene(std::shared_ptr<Scene> scene)
{
m_scene = scene;
m_scene->add_listener(shared_from_this());
}
const Scene * get_scene() const { return m_scene.get(); }
void add_display(std::shared_ptr<Display> disp)
{
m_displays.emplace_back(disp);
cleanup(m_displays);
}
void remove_displays() { m_displays = {}; }
void on_scene_updated(const Scene &scene) override;
void on_left_click_down() override { m_left_btn = true; }
void on_left_click_up() override { m_left_btn = false; }
void on_right_click_down() override { m_right_btn = true; }
void on_right_click_up() override { m_right_btn = false; }
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
void on_moved_to(long x, long y) override;
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
};
}} // namespace Slic3r::GL
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
-63
View File
@@ -1,63 +0,0 @@
#include "ShaderCSGDisplay.hpp"
#include "libslic3r/SLAPrint.hpp"
#include <GL/glew.h>
namespace Slic3r { namespace GL {
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
{
auto v = std::make_shared<CSGVolume>();
v->load_mesh(mesh);
m_volumes.emplace_back(v);
}
void ShaderCSGDisplay::render_scene()
{
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
glColor4fv(color);
glDepthFunc(GL_LESS);
for (auto &v : m_volumes) v->render();
glFlush();
}
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
{
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
// Look at CSGDisplay::on_scene_updated to see how its done there.
const SLAPrint *print = scene.get_print();
if (!print) return;
m_volumes.clear();
for (const SLAPrintObject *po : print->objects()) {
const ModelObject *mo = po->model_object();
TriangleMesh msh = mo->raw_mesh();
sla::DrainHoles holedata = mo->sla_drain_holes;
for (const ModelInstance *mi : mo->instances) {
TriangleMesh mshinst = msh;
auto interior = po->hollowed_interior_mesh();
interior.transform(po->trafo().inverse());
mshinst.merge(interior);
mi->transform_mesh(&mshinst);
auto bb = mshinst.bounding_box();
auto center = bb.center().cast<float>();
mshinst.translate(-center);
add_mesh(mshinst);
}
for (const sla::DrainHole &holept : holedata)
add_mesh(sla::to_triangle_mesh(holept.to_mesh()));
}
repaint();
}
}} // namespace Slic3r::GL
-27
View File
@@ -1,27 +0,0 @@
#ifndef SHADERCSGDISPLAY_HPP
#define SHADERCSGDISPLAY_HPP
#include "Engine.hpp"
namespace Slic3r { namespace GL {
class CSGVolume: public Volume
{
// Extend...
};
class ShaderCSGDisplay: public Display {
protected:
vector<std::shared_ptr<CSGVolume>> m_volumes;
void add_mesh(const TriangleMesh &mesh);
public:
void render_scene() override;
void on_scene_updated(const Scene &scene) override;
};
}}
#endif // SHADERCSGDISPLAY_HPP
-734
View File
@@ -1,734 +0,0 @@
#include <iostream>
#include <utility>
#include <memory>
#include "Engine.hpp"
#include "ShaderCSGDisplay.hpp"
#include <GL/glew.h>
#include <opencsg/opencsg.h>
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/slider.h>
#include <wx/tglbtn.h>
#include <wx/combobox.h>
#include <wx/spinctrl.h>
#include <wx/msgdlg.h>
#include <wx/glcanvas.h>
#include <wx/cmdline.h>
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "slic3r/GUI/Jobs/Job.hpp"
#include "slic3r/GUI/ProgressStatusBar.hpp"
using namespace Slic3r::GL;
class Renderer {
protected:
wxGLCanvas *m_canvas;
std::shared_ptr<wxGLContext> m_context;
public:
Renderer(wxGLCanvas *c): m_canvas{c} {
auto ctx = new wxGLContext(m_canvas);
if (!ctx || !ctx->IsOK()) {
wxMessageBox("Could not create OpenGL context.", "Error",
wxOK | wxICON_ERROR);
return;
}
m_context.reset(ctx);
}
wxGLContext * context() { return m_context.get(); }
const wxGLContext * context() const { return m_context.get(); }
};
// Tell the CSGDisplay how to swap buffers and set the gl context.
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
public:
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
void set_active(long w, long h) override
{
m_canvas->SetCurrent(*m_context);
Slic3r::GL::Display::set_active(w, h);
}
void swap_buffers() override { m_canvas->SwapBuffers(); }
};
// Tell the CSGDisplay how to swap buffers and set the gl context.
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
public:
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
void set_active(long w, long h) override
{
m_canvas->SetCurrent(*m_context);
Slic3r::GL::Display::set_active(w, h);
}
void swap_buffers() override { m_canvas->SwapBuffers(); }
};
// The opengl rendering facility. Here we implement the rendering objects.
class Canvas: public wxGLCanvas
{
// One display is active at a time, the OCSGRenderer by default.
std::shared_ptr<Slic3r::GL::Display> m_display;
public:
template<class...Args>
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
std::shared_ptr<Slic3r::GL::Display> get_display() const { return m_display; }
void set_display(std::shared_ptr<Slic3r::GL::Display> d) { m_display = d; }
};
// Enumerate possible mouse events, we will record them.
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
struct Event
{
EEvents type;
long a, b;
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
};
// Create a special mouse input adapter, which can store (record) the received
// mouse signals into a file and play back the stored events later.
class RecorderMouseInput: public MouseInput {
std::vector<Event> m_events;
bool m_recording = false, m_playing = false;
public:
void left_click_down() override
{
if (m_recording) m_events.emplace_back(LCLK_D);
if (!m_playing) MouseInput::left_click_down();
}
void left_click_up() override
{
if (m_recording) m_events.emplace_back(LCLK_U);
if (!m_playing) MouseInput::left_click_up();
}
void right_click_down() override
{
if (m_recording) m_events.emplace_back(RCLK_D);
if (!m_playing) MouseInput::right_click_down();
}
void right_click_up() override
{
if (m_recording) m_events.emplace_back(RCLK_U);
if (!m_playing) MouseInput::right_click_up();
}
void double_click() override
{
if (m_recording) m_events.emplace_back(DDCLK);
if (!m_playing) MouseInput::double_click();
}
void scroll(long v, long d, WheelAxis wa) override
{
if (m_recording) m_events.emplace_back(SCRL, v, d);
if (!m_playing) MouseInput::scroll(v, d, wa);
}
void move_to(long x, long y) override
{
if (m_recording) m_events.emplace_back(MV, x, y);
if (!m_playing) MouseInput::move_to(x, y);
}
void save(std::ostream &stream)
{
for (const Event &evt : m_events)
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
}
void load(std::istream &stream)
{
m_events.clear();
while (stream.good()) {
int type; long a, b;
stream >> type >> a >> b;
m_events.emplace_back(EEvents(type), a, b);
}
}
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
void play()
{
m_playing = true;
for (const Event &evt : m_events) {
switch (evt.type) {
case LCLK_U: MouseInput::left_click_up(); break;
case LCLK_D: MouseInput::left_click_down(); break;
case RCLK_U: MouseInput::right_click_up(); break;
case RCLK_D: MouseInput::right_click_down(); break;
case DDCLK: MouseInput::double_click(); break;
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
case MV: MouseInput::move_to(evt.a, evt.b); break;
}
wxTheApp->Yield();
if (!m_playing)
break;
}
m_playing = false;
}
void stop() { m_playing = false; }
bool is_playing() const { return m_playing; }
};
// The top level frame of the application.
class MyFrame: public wxFrame
{
// Instantiate the 3D engine.
std::shared_ptr<Scene> m_scene; // Model
std::shared_ptr<Canvas> m_canvas; // Views store
std::shared_ptr<OCSGRenderer> m_ocsgdisplay; // View
std::shared_ptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
std::shared_ptr<Controller> m_ctl; // Controller
// Add a status bar with progress indication.
std::shared_ptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
RecorderMouseInput m_mouse;
// When loading a Model from 3mf and preparing it, we use a separate thread.
class SLAJob: public Slic3r::GUI::Job {
MyFrame *m_parent;
std::unique_ptr<Slic3r::SLAPrint> m_print;
std::string m_fname;
public:
SLAJob(MyFrame *frame, const std::string &fname)
: Slic3r::GUI::Job{frame->m_stbar}
, m_parent{frame}
, m_fname{fname}
{}
// Runs in separate thread
void process() override;
const std::string & get_project_fname() const { return m_fname; }
protected:
// Runs in the UI thread.
void finalize() override
{
m_parent->m_scene->set_print(std::move(m_print));
m_parent->m_stbar->set_status_text(
wxString::Format("Model %s loaded.", m_fname));
}
};
std::unique_ptr<SLAJob> m_ui_job;
// To keep track of the running average of measured fps values.
double m_fps_avg = 0.;
// We need the record button across methods
wxToggleButton *m_record_btn;
wxComboBox * m_alg_select;
wxComboBox * m_depth_select;
wxComboBox * m_optimization_select;
wxSpinCtrl * m_convexity_spin;
wxToggleButton *m_csg_toggle;
wxToggleButton *m_ms_toggle;
wxStaticText *m_fpstext;
CSGSettings m_csg_settings;
void read_csg_settings(const wxCmdLineParser &parser);
void set_renderer_algorithm(const wxString &alg);
void activate_canvas_display();
public:
MyFrame(const wxString & title,
const wxPoint & pos,
const wxSize & size,
const wxCmdLineParser &parser);
// Grab a 3mf and load (hollow it out) within the UI job.
void load_model(const std::string &fname) {
m_ui_job = std::make_unique<SLAJob>(this, fname);
m_ui_job->start();
}
// Load a previously stored mouse event log and play it back.
void play_back_mouse(const std::string &events_fname)
{
std::fstream stream(events_fname, std::fstream::in);
if (stream.good()) {
std::string model_name;
std::getline(stream, model_name);
load_model(model_name);
while (!m_ui_job->is_finalized())
wxTheApp->Yield();;
int w, h;
stream >> w >> h;
SetSize(w, h);
m_mouse.load(stream);
if (m_record_btn) m_record_btn->Disable();
m_mouse.play();
}
}
Canvas * canvas() { return m_canvas.get(); }
const Canvas * canvas() const { return m_canvas.get(); }
// Bind the canvas mouse events to a class implementing MouseInput interface
void bind_canvas_events(MouseInput &msinput);
double get_fps_average() const { return m_fps_avg; }
};
// Possible OpenCSG configuration values. Will be used on the command line and
// on the UI widgets.
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
{
auto it = std::find(v.begin(), v.end(), a.ToStdString());
return it - v.begin();
};
class App : public wxApp {
MyFrame *m_frame = nullptr;
wxString m_fname;
public:
bool OnInit() override {
wxCmdLineParser parser(argc, argv);
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
parser.Parse();
bool is_play = parser.Found("play", &m_fname);
m_frame = new MyFrame("OrcaSlicer OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
if (is_play) {
Bind(wxEVT_IDLE, &App::Play, this);
m_frame->Show( true );
} else m_frame->Show( true );
return true;
}
void Play(wxIdleEvent &) {
Unbind(wxEVT_IDLE, &App::Play, this);
m_frame->play_back_mouse(m_fname.ToStdString());
m_frame->Destroy();
}
};
wxIMPLEMENT_APP(App);
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
{
wxString alg;
parser.Found("algorithm", &alg);
wxString depth;
parser.Found("depth", &depth);
wxString opt;
parser.Found("optimization", &opt);
long convexity = 1;
parser.Found("convexity", &convexity);
bool csg_off = parser.Found("disable-csg");
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
m_csg_settings.set_convexity(unsigned(convexity));
m_csg_settings.enable_csg(!csg_off);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
}
void MyFrame::set_renderer_algorithm(const wxString &alg)
{
long alg_idx = get_idx(alg, CSG_ALGS);
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
// If there is a valid display in place, save its camera.
auto cam = m_canvas->get_display() ?
m_canvas->get_display()->get_camera() : nullptr;
if (alg == "EnricoShader") {
m_alg_select->SetSelection(int(alg_idx));
m_depth_select->Disable();
m_optimization_select->Disable();
m_csg_toggle->Disable();
m_ocsgdisplay.reset();
canvas()->set_display(nullptr);
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
canvas()->set_display(m_shadercsg_display);
} else {
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
m_alg_select->SetSelection(m_csg_settings.get_algo());
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
m_optimization_select->Enable();
m_csg_toggle->Enable();
m_shadercsg_display.reset();
canvas()->set_display(nullptr);
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
canvas()->set_display(m_ocsgdisplay);
}
if (cam)
m_canvas->get_display()->set_camera(cam);
m_ctl->remove_displays();
m_ctl->add_display(m_canvas->get_display());
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
});
if (IsShown()) {
activate_canvas_display();
m_canvas->get_display()->on_scene_updated(*m_scene);
}
}
void MyFrame::activate_canvas_display()
{
const wxSize ClientSize = m_canvas->GetClientSize();
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
enable_multisampling(m_ms_toggle->GetValue());
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
// This is required even though dc is not used otherwise.
wxPaintDC dc(m_canvas.get());
const wxSize csize = m_canvas->GetClientSize();
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
m_canvas->get_display()->repaint();
});
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
const wxSize csize = m_canvas->GetClientSize();
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
m_canvas->get_display()->repaint();
});
// Do the repaint continuously
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
m_canvas->get_display()->repaint();
evt.RequestMore();
});
bind_canvas_events(m_mouse);
}
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
const wxCmdLineParser &parser):
wxFrame(nullptr, wxID_ANY, title, pos, size)
{
wxMenu *menuFile = new wxMenu;
menuFile->Append(wxID_OPEN);
menuFile->Append(wxID_EXIT);
wxMenuBar *menuBar = new wxMenuBar;
menuBar->Append( menuFile, "&File" );
SetMenuBar( menuBar );
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
m_stbar->embed(this);
SetStatusText( "Welcome to wxWidgets!" );
int attribList[] =
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
// RGB channels each should be allocated with 8 bit depth. One
// should almost certainly get these bit depths by default.
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
// drivers would most likely work with some alpha plane, but
// glReadPixels would not return the alpha channel on NVIDIA if
// not requested when the GL context is created.
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
m_scene = std::make_shared<Scene>();
m_ctl = std::make_shared<Controller>();
m_ctl->set_scene(m_scene);
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
wxDefaultPosition, wxDefaultSize,
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
read_csg_settings(parser);
wxPanel *control_panel = new wxPanel(this);
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
auto console_sizer = new wxBoxSizer(wxVERTICAL);
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
wxDefaultPosition, wxDefaultSize,
wxSL_VERTICAL);
slider_sizer->Add(slider, 1, wxEXPAND);
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
m_csg_toggle->SetValue(true);
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
auto add_combobox = [control_panel, console_sizer]
(const wxString &label, const std::vector<wxString> &list)
{
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
wxDefaultPosition, wxDefaultSize,
int(list.size()), list.data());
auto sz = new wxBoxSizer(wxHORIZONTAL);
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
wxALL | wxALIGN_CENTER, 5);
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
console_sizer->Add(sz, 0, wxEXPAND);
return widget;
};
auto add_spinctl = [control_panel, console_sizer]
(const wxString &label, int initial, int min, int max)
{
auto widget = new wxSpinCtrl(
control_panel, wxID_ANY,
wxString::Format("%d", initial),
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
initial);
auto sz = new wxBoxSizer(wxHORIZONTAL);
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
wxALL | wxALIGN_CENTER, 5);
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
console_sizer->Add(sz, 0, wxEXPAND);
return widget;
};
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
m_optimization_select = add_combobox("Optimization", CSG_OPT);
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
console_sizer->Add(m_fpstext, 0, wxALL, 5);
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
controlsizer->Add(slider_sizer, 0, wxEXPAND);
controlsizer->Add(console_sizer, 1, wxEXPAND);
control_panel->SetSizer(controlsizer);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_canvas.get(), 1, wxEXPAND);
sizer->Add(control_panel, 0, wxEXPAND);
SetSizer(sizer);
wxString alg;
if (!parser.Found("algorithm", &alg)) alg = "Auto";
set_renderer_algorithm(alg);
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
if (m_canvas) RemoveChild(m_canvas.get());
m_canvas.reset();
if (!m_mouse.is_playing()) evt.Skip();
else m_mouse.stop();
});
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
wxFileDialog dlg(this, "Select project file", wxEmptyString,
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
}, wxID_OPEN);
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
activate_canvas_display();
});
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
m_ctl->move_clip_plane(double(slider->GetValue()));
});
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
enable_multisampling(m_ms_toggle->GetValue());
m_canvas->get_display()->repaint();
});
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
stt.enable_csg(m_csg_toggle->GetValue());
m_ocsgdisplay->apply_csgsettings(stt);
});
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
wxString alg = m_alg_select->GetValue();
int sel = m_alg_select->GetSelection();
m_csg_settings.set_algo(sel);
set_renderer_algorithm(alg);
});
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
int sel = m_depth_select->GetSelection();
m_csg_settings.set_depth_algo(sel);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
});
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
int sel = m_optimization_select->GetSelection();
m_csg_settings.set_optimization(sel);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
});
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
int c = m_convexity_spin->GetValue();
if (c > 0) {
m_csg_settings.set_convexity(unsigned(c));
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
}
});
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
if (!m_ui_job) {
m_stbar->set_status_text("No project loaded!");
return;
}
if (m_record_btn->GetValue()) {
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
m_ctl->on_scene_updated(*m_scene);
m_mouse.record(true);
} else {
m_mouse.record(false);
wxFileDialog dlg(this, "Select output file",
wxEmptyString, wxEmptyString, "*.events",
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
if (dlg.ShowModal() == wxID_OK) {
std::fstream stream(dlg.GetPath().ToStdString(),
std::fstream::out);
if (stream.good()) {
stream << m_ui_job->get_project_fname() << "\n";
wxSize winsize = GetSize();
stream << winsize.x << " " << winsize.y << "\n";
m_mouse.save(stream);
}
}
}
});
}
void MyFrame::bind_canvas_events(MouseInput &ms)
{
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
Slic3r::GL::MouseInput::waVertical :
Slic3r::GL::MouseInput::waHorizontal);
});
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
});
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
ms.right_click_down();
});
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
ms.right_click_up();
});
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
ms.left_click_down();
});
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
ms.left_click_up();
});
ms.add_listener(m_ctl);
}
void MyFrame::SLAJob::process()
{
using SlStatus = Slic3r::PrintBase::SlicingStatus;
Slic3r::DynamicPrintConfig cfg;
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
m_print = std::make_unique<Slic3r::SLAPrint>();
m_print->apply(model, cfg);
Slic3r::PrintBase::TaskParams params;
params.to_object_step = Slic3r::slaposHollowing;
m_print->set_task(params);
m_print->set_status_callback([this](const SlStatus &status) {
update_status(status.percent, status.text);
});
try {
m_print->process();
} catch(std::exception &e) {
update_status(0, wxString("Exception during processing: ") + e.what());
}
}
//int main() {}
-4
View File
@@ -23,15 +23,11 @@ RUN apt-get update && apt-get install -y \
libcairo2-dev \ libcairo2-dev \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libdbus-1-dev \ libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \ libgstreamer1.0-dev \
libgstreamerd-3-dev \ libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \ libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-good1.0-dev \ libgstreamer-plugins-good1.0-dev \
libgtk-3-dev \ libgtk-3-dev \
libgtk-3-dev \
libsecret-1-dev \ libsecret-1-dev \
libsoup2.4-dev \ libsoup2.4-dev \
libssl3 \ libssl3 \
-2
View File
@@ -31,8 +31,6 @@ RUN apt-get update && apt-get install -y \
libcairo2-dev \ libcairo2-dev \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libdbus-1-dev \ libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \ libgstreamer1.0-dev \
libgstreamerd-3-dev \ libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \ libgstreamer-plugins-base1.0-dev \
@@ -55,21 +55,6 @@ modules:
url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz
sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39 sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39
- name: glu
build-options:
cxxflags: -Wno-register
config-opts:
- --disable-static
sources:
- type: archive
url: https://ftp.osuosl.org/pub/blfs/conglomeration/glu/glu-9.0.2.tar.xz
sha256: 6e7280ff585c6a1d9dfcdf2fca489251634b3377bfc33c29e4002466a38d02d4
cleanup:
- /include
- /lib/*.a
- /lib/*.la
- /lib/pkgconfig
- name: kde-extra-cmake-modules - name: kde-extra-cmake-modules
buildsystem: cmake-ninja buildsystem: cmake-ninja
sources: sources:
@@ -217,12 +202,6 @@ modules:
sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a
dest: external-packages/GLFW dest: external-packages/GLFW
# OpenCSG 1.4.2
- type: file
url: https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
sha256: 51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
dest: external-packages/OpenCSG
# SolveSpace libslvs (2D sketch constraint solver, Design tab) # SolveSpace libslvs (2D sketch constraint solver, Design tab)
- type: file - type: file
url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip
-1
View File
@@ -11,7 +11,6 @@ export REQUIRED_DEV_PACKAGES=(
file file
gettext gettext
git git
glew
gst-plugins-good gst-plugins-good
gstreamer gstreamer
gtk3 gtk3
-1
View File
@@ -11,7 +11,6 @@ export REQUIRED_DEV_PACKAGES=(
file file
gettext gettext
git git
glew
gst-plugins-good gst-plugins-good
gstreamer gstreamer
gtk3 gtk3
+1 -2
View File
@@ -6,10 +6,9 @@ export REQUIRED_BUNDLES=(
c-basic c-basic
dev-utils dev-utils
devpkg-curl devpkg-curl
devpkg-glew
devpkg-glu
devpkg-gstreamer devpkg-gstreamer
devpkg-gtk3 devpkg-gtk3
devpkg-libglvnd
devpkg-libmspack devpkg-libmspack
devpkg-libsecret devpkg-libsecret
devpkg-openssl devpkg-openssl
+1 -1
View File
@@ -14,7 +14,7 @@ REQUIRED_DEV_PACKAGES=(
gstreamer1.0-gtk3 gstreamer1.0-gtk3
libcurl4-openssl-dev libcurl4-openssl-dev
libdbus-1-dev libdbus-1-dev
libglew-dev libgl-dev
libgstreamerd-3-dev libgstreamerd-3-dev
libgtk-3-dev libgtk-3-dev
libmspack-dev libmspack-dev
+1 -1
View File
@@ -22,7 +22,7 @@ REQUIRED_DEV_PACKAGES=(
libspnav-devel libspnav-devel
libtool libtool
m4 m4
mesa-libGLU-devel mesa-libGL-devel
ninja-build ninja-build
openssl-devel openssl-devel
perl-FindBin perl-FindBin
+1 -1
View File
@@ -18,7 +18,6 @@ REQUIRED_DEV_PACKAGES=(
dev-vcs/git dev-vcs/git
gui-libs/eglexternalplatform gui-libs/eglexternalplatform
kde-frameworks/extra-cmake-modules kde-frameworks/extra-cmake-modules
media-libs/glew
media-libs/gst-plugins-base:1.0 media-libs/gst-plugins-base:1.0
media-libs/gstreamer:1.0 media-libs/gstreamer:1.0
media-plugins/gst-plugins-gtk:1.0 media-plugins/gst-plugins-gtk:1.0
@@ -31,6 +30,7 @@ REQUIRED_DEV_PACKAGES=(
sys-devel/gettext sys-devel/gettext
sys-devel/m4 sys-devel/m4
virtual/libudev virtual/libudev
virtual/opengl
x11-libs/gtk+:3 x11-libs/gtk+:3
dev-util/pkgconf dev-util/pkgconf
dev-lang/yasm dev-lang/yasm
+1 -1
View File
@@ -21,7 +21,7 @@ REQUIRED_DEV_PACKAGES=(
libspnav-devel libspnav-devel
libtool libtool
m4 m4
glu-devel Mesa-libGL-devel
ninja-build ninja-build
openssl-devel openssl-devel
perl-FindBin-Real perl-FindBin-Real
-1
View File
@@ -189,7 +189,6 @@ else ()
target_link_libraries(OrcaSlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads pangoft2-1.0) target_link_libraries(OrcaSlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads pangoft2-1.0)
endif () endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
if (SLIC3R_GUI) if (SLIC3R_GUI)
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES}) # target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector) target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector)
@@ -344,7 +344,7 @@ TARGET_BIN="\$1"
if target_missing_runtime_library "\$TARGET_BIN" "libOpenGL.so.0" || ! has_host_runtime_library "libOpenGL.so.0"; then if target_missing_runtime_library "\$TARGET_BIN" "libOpenGL.so.0" || ! has_host_runtime_library "libOpenGL.so.0"; then
echo "Error: missing host OpenGL runtime library libOpenGL.so.0." >&2 echo "Error: missing host OpenGL runtime library libOpenGL.so.0." >&2
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0 and libglu1-mesa" >&2 echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0" >&2
echo "On Arch/CachyOS, install: libglvnd" >&2 echo "On Arch/CachyOS, install: libglvnd" >&2
exit 1 exit 1
fi fi
+3
View File
@@ -411,6 +411,9 @@ void AppConfig::set_defaults()
if (get("show_overhang").empty()) if (get("show_overhang").empty())
set_bool("show_overhang", false); set_bool("show_overhang", false);
if (get("show_center_of_mass").empty())
set_bool("show_center_of_mass", false);
#ifdef _WIN32 #ifdef _WIN32
//#ifdef SUPPORT_3D_CONNEXION //#ifdef SUPPORT_3D_CONNEXION
+4
View File
@@ -112,6 +112,8 @@ set(lisbslic3r_sources
CommonDefs.hpp CommonDefs.hpp
Config.cpp Config.cpp
Config.hpp Config.hpp
ConnectedBodies.cpp
ConnectedBodies.hpp
ContourZ.cpp ContourZ.cpp
CustomGCode.cpp CustomGCode.cpp
CustomGCode.hpp CustomGCode.hpp
@@ -180,6 +182,8 @@ set(lisbslic3r_sources
Fill/FillPlanePath.hpp Fill/FillPlanePath.hpp
Fill/FillRectilinear.cpp Fill/FillRectilinear.cpp
Fill/FillRectilinear.hpp Fill/FillRectilinear.hpp
Fill/FillTpmsAdaptive.cpp
Fill/FillTpmsAdaptive.hpp
Fill/FillTpmsD.cpp Fill/FillTpmsD.cpp
Fill/FillTpmsD.hpp Fill/FillTpmsD.hpp
Fill/FillTpmsFK.cpp Fill/FillTpmsFK.cpp
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Slic3r { namespace csg {
// A CSGPartT should be an object that can provide at least a mesh + trafo and an // A CSGPartT should be an object that can provide at least a mesh + trafo and an
// associated csg operation. A collection of CSGPartT objects can then // associated csg operation. A collection of CSGPartT objects can then
// be interpreted as one model and used in various contexts. It can be assembled // be interpreted as one model and used in various contexts. It can be assembled
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to // with CGAL or OpenVDB or provided to a ray-tracer to
// deal with various parts of it according to the supported CSG types... // deal with various parts of it according to the supported CSG types...
// //
// A few simple templated interface functions are provided here and a default // A few simple templated interface functions are provided here and a default
+292
View File
@@ -0,0 +1,292 @@
#include "ConnectedBodies.hpp"
#include "AABBTreeIndirect.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
#include "Geometry/ConvexHull.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "TriangleMesh.hpp"
#include "TriangleMeshSlicer.hpp"
#include "libslic3r.h"
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <limits>
#include <utility>
#include <vector>
namespace Slic3r {
std::vector<std::vector<size_t>> connected_bodies(const std::vector<const ExPolygons *> &layers, size_t &count,
const std::function<void()> &throw_if_canceled)
{
// Union-find over the islands of all layers, numbered layer after layer.
std::vector<size_t> first(layers.size() + 1, 0);
for (size_t l = 0; l < layers.size(); ++l)
first[l + 1] = first[l] + layers[l]->size();
std::vector<size_t> parent(first.back());
for (size_t i = 0; i < parent.size(); ++i)
parent[i] = i;
const auto find = [&parent](size_t i) {
while (parent[i] != i)
i = parent[i] = parent[parent[i]];
return i;
};
std::vector<std::vector<BoundingBox>> boxes(layers.size());
for (size_t l = 0; l < layers.size(); ++l)
for (const ExPolygon &island : *layers[l])
boxes[l].emplace_back(get_extents(island));
for (size_t l = 0; l + 1 < layers.size(); ++l) {
if (throw_if_canceled)
throw_if_canceled();
// Index the smaller of the two layers, so that a fragmented layer is not scanned island by island.
size_t a_layer = l;
size_t b_layer = l + 1;
if (layers[a_layer]->size() < layers[b_layer]->size())
std::swap(a_layer, b_layer);
if (layers[b_layer]->empty())
continue;
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> wrappers;
wrappers.reserve(boxes[b_layer].size());
for (size_t b = 0; b < boxes[b_layer].size(); ++b)
wrappers.emplace_back(b, boxes[b_layer][b]);
IslandTree tree;
tree.build_modify_input(wrappers);
for (size_t a = 0; a < boxes[a_layer].size(); ++a) {
const IslandTree::BoundingBox query(boxes[a_layer][a].min, boxes[a_layer][a].max);
AABBTreeIndirect::traverse(
tree, [&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
[&](const IslandTree::Node &node) {
// The tree's boxes are widened by an epsilon, and islands already joined need no clipping.
const size_t b = node.idx;
if (boxes[a_layer][a].overlap(boxes[b_layer][b]) && find(first[a_layer] + a) != find(first[b_layer] + b) &&
!intersection_ex((*layers[a_layer])[a], (*layers[b_layer])[b]).empty())
parent[find(first[a_layer] + a)] = find(first[b_layer] + b);
return true;
});
}
}
std::vector<size_t> body(parent.size(), std::numeric_limits<size_t>::max());
std::vector<std::vector<size_t>> out(layers.size());
count = 0;
for (size_t l = 0; l < layers.size(); ++l)
for (size_t i = 0; i < layers[l]->size(); ++i) {
size_t &b = body[find(first[l] + i)];
if (b == std::numeric_limits<size_t>::max())
b = count++;
out[l].emplace_back(b);
}
return out;
}
IslandLocator::IslandLocator(const ExPolygons &islands, coord_t margin) : m_islands(&islands), m_alone(islands.size(), true)
{
m_boxes.reserve(islands.size());
for (const ExPolygon &island : islands)
m_boxes.emplace_back(get_extents(island).inflated(margin));
// Sweep the boxes along x, so that only those reaching each other are compared.
std::vector<size_t> order(m_boxes.size());
for (size_t i = 0; i < order.size(); ++i)
order[i] = i;
std::sort(order.begin(), order.end(), [this](size_t l, size_t r) { return m_boxes[l].min.x() < m_boxes[r].min.x(); });
for (size_t a = 0; a < order.size(); ++a)
for (size_t b = a + 1; b < order.size() && m_boxes[order[b]].min.x() <= m_boxes[order[a]].max.x(); ++b)
if (m_boxes[order[a]].overlap(m_boxes[order[b]]))
m_alone[order[a]] = m_alone[order[b]] = false;
}
bool IslandLocator::holds(size_t island, const Point &point, bool strict) const
{
return m_boxes[island].contains(point) && ((m_alone[island] && !strict) || (*m_islands)[island].contains(point));
}
std::pair<int, double> IslandLocator::find(const Point &point, bool strict) const
{
int nearest = -1;
double distance = std::numeric_limits<double>::max();
for (size_t i = 0; i < m_boxes.size(); ++i)
if (m_boxes[i].contains(point)) {
if ((m_alone[i] && !strict) || (*m_islands)[i].contains(point))
return { int(i), 0. };
if (const double d = ((*m_islands)[i].point_projection(point) - point).cast<double>().squaredNorm(); d < distance) {
distance = d;
nearest = int(i);
}
}
return { nearest, distance };
}
// The area of polygons and their first and second moments of area, which holes, running clockwise, subtract.
struct AreaMoments
{
double area{ 0. };
Vec2d first{ Vec2d::Zero() };
// Of x^2, y^2 and xy.
Vec3d second{ Vec3d::Zero() };
void add(const Polygon &polygon)
{
if (polygon.points.size() < 3)
return;
Vec2d p1 = unscaled(polygon.points.back());
for (const Point &point : polygon.points) {
const Vec2d p2 = unscaled(point);
const double a = cross2(p1, p2);
area += a / 2.;
first += a / 6. * (p1 + p2);
second += a / 12. *
Vec3d(p1.x() * p1.x() + p1.x() * p2.x() + p2.x() * p2.x(), p1.y() * p1.y() + p1.y() * p2.y() + p2.y() * p2.y(),
p1.x() * p1.y() + p2.x() * p2.y() + 0.5 * (p1.x() * p2.y() + p2.x() * p1.y()));
p1 = p2;
}
}
};
// Mass, volume and the first and second moments of mass about the origin.
struct Moments
{
double mass{ 0. };
double volume{ 0. };
Vec3d first{ Vec3d::Zero() };
Matrix3d second{ Matrix3d::Zero() };
void add(const Moments &other)
{
mass += other.mass;
volume += other.volume;
first += other.first;
second += other.second;
}
};
BoundingBoxf3 SolidBody::bounding_box(const Transform3d &trafo) const
{
BoundingBoxf3 box;
for (const Point &point : hull.points)
for (const double z : { z_min, z_max })
box.merge(trafo * Vec3d(unscaled(point.x()), unscaled(point.y()), z));
return box;
}
std::vector<SolidBody> solid_bodies(const std::vector<MeshInPlace> &solids, const std::vector<double> &densities,
const std::vector<MeshInPlace> &negatives, size_t slabs)
{
assert(densities.size() == solids.size());
double z_min = std::numeric_limits<double>::max();
double z_max = std::numeric_limits<double>::lowest();
for (const auto &[mesh, trafo] : solids)
for (const stl_vertex &v : mesh->vertices) {
const double z = (trafo * v.cast<double>()).z();
z_min = std::min(z_min, z);
z_max = std::max(z_max, z);
}
if (z_min >= z_max || slabs == 0)
return {};
// Each slab sliced at its middle.
const double thickness = (z_max - z_min) / double(slabs);
std::vector<float> zs(slabs);
for (size_t k = 0; k < slabs; ++k)
zs[k] = float(z_min + (double(k) + 0.5) * thickness);
MeshSlicingParamsEx params;
const auto slice = [&zs, &params](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 &region, 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 &region : 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
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "TriangleMesh.hpp"
#include "libslic3r.h"
#include <admesh/stl.h>
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
namespace Slic3r {
// The connected body of each island of each layer: islands of adjacent layers whose slices overlap are one body.
// Bodies are numbered from 0 in the order of their first island.
std::vector<std::vector<size_t>> connected_bodies(const std::vector<const ExPolygons *> &layers, size_t &count,
const std::function<void()> &throw_if_canceled = nullptr);
// Finds the island of a layer that a point lies in, testing the polygons only where the boxes of several islands hold it.
class IslandLocator
{
public:
// The islands must outlive the locator. Their boxes are widened by the margin, for points reaching past an outline.
IslandLocator(const ExPolygons &islands, coord_t margin);
// Whether the island holds the point, or its box does where no other box reaches unless strict.
bool holds(size_t island, const Point &point, bool strict = false) const;
// The island holding the point as above, else the nearest one whose box holds it, with the squared distance to it; -1
// for none.
std::pair<int, double> find(const Point &point, bool strict = false) const;
const std::vector<BoundingBox> &boxes() const { return m_boxes; }
private:
const ExPolygons *m_islands;
std::vector<BoundingBox> m_boxes;
std::vector<bool> m_alone;
};
using MeshInPlace = std::pair<const indexed_triangle_set *, Transform3d>;
// A connected body of solids, with its outline seen from above as a convex hull and the height it spans.
struct SolidBody : MassProperties
{
Polygon hull;
double z_min{ 0. };
double z_max{ 0. };
// Its box once transformed, tight for a transformation that rotates about z only.
BoundingBoxf3 bounding_box(const Transform3d &trafo) const;
};
// Each connected body of the union of the solids less the negatives, sliced into slabs, each solid weighing its density.
// Where solids overlap, the later one counts, as slicing prints it.
std::vector<SolidBody> solid_bodies(const std::vector<MeshInPlace> &solids, const std::vector<double> &densities,
const std::vector<MeshInPlace> &negatives, size_t slabs);
} // namespace Slic3r
+33 -6
View File
@@ -311,6 +311,11 @@ struct SurfaceFillParams
// For Gyroid: when true, use the parameterized "optimized" wave. // For Gyroid: when true, use the parameterized "optimized" wave.
bool gyroid_optimized = false; bool gyroid_optimized = false;
// For TPMS: grade the density from the surface to the interior of the object.
TpmsAdaptiveMode tpms_adaptive = TpmsAdaptiveMode::Disabled;
float tpms_interior_density = 0.f;
TpmsAdaptiveGradient tpms_adaptive_gradient = TpmsAdaptiveGradient::Linear;
// Orca: corner smoothing factor in the range [0, 1]. // Orca: corner smoothing factor in the range [0, 1].
double smooth_factor { 0. }; double smooth_factor { 0. };
@@ -353,6 +358,9 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
RETURN_COMPARE_NON_EQUAL(gyroid_optimized); RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
RETURN_COMPARE_NON_EQUAL(tpms_adaptive);
RETURN_COMPARE_NON_EQUAL(tpms_interior_density);
RETURN_COMPARE_NON_EQUAL(tpms_adaptive_gradient);
RETURN_COMPARE_NON_EQUAL(smooth_factor); RETURN_COMPARE_NON_EQUAL(smooth_factor);
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern); RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
RETURN_COMPARE_NON_EQUAL(separated_infills); RETURN_COMPARE_NON_EQUAL(separated_infills);
@@ -386,6 +394,9 @@ struct SurfaceFillParams
this->center_of_surface_pattern == rhs.center_of_surface_pattern && this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
this->separated_infills == rhs.separated_infills && this->separated_infills == rhs.separated_infills &&
this->gyroid_optimized == rhs.gyroid_optimized && this->gyroid_optimized == rhs.gyroid_optimized &&
this->tpms_adaptive == rhs.tpms_adaptive &&
this->tpms_interior_density == rhs.tpms_interior_density &&
this->tpms_adaptive_gradient == rhs.tpms_adaptive_gradient &&
this->smooth_factor == rhs.smooth_factor && this->smooth_factor == rhs.smooth_factor &&
this->fill_order == rhs.fill_order; this->fill_order == rhs.fill_order;
} }
@@ -994,15 +1005,23 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
// Orca: apply fill multiline only for sparse infill // Orca: apply fill multiline only for sparse infill
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1; params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
// Pass through gyroid_optimized only when the effective pattern is Gyroid, // Orca: Pass through separated_infills only where it can move the pattern.
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
// (which would unnecessarily split fill batching).
// Stored on SurfaceFillParams; copied to FillParams during conversion.
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
// Orca: Likewise separated_infills only where it can move the pattern.
params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) && params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) &&
params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface; params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface;
// Orca: only the TPMS sparse infill is graded; reset otherwise, as params is reused.
params.tpms_adaptive = is_tpms_adaptive_pattern(params.pattern) && params.extrusion_role == erInternalInfill ?
region_config.tpms_adaptive.value : TpmsAdaptiveMode::Disabled;
const bool tpms_adaptive = params.tpms_adaptive != TpmsAdaptiveMode::Disabled;
params.tpms_interior_density = tpms_adaptive ? std::max(1.f, float(region_config.tpms_interior_density)) : 0.f;
params.tpms_adaptive_gradient = tpms_adaptive ? region_config.tpms_adaptive_gradient.value : TpmsAdaptiveGradient::Linear;
// Pass through gyroid_optimized only when the effective pattern is Gyroid,
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
// (which would unnecessarily split fill batching). Adaptive density replaces it.
// Stored on SurfaceFillParams; copied to FillParams during conversion.
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized && !tpms_adaptive;
if (params.extrusion_role == erInternalInfill) { if (params.extrusion_role == erInternalInfill) {
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value, params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
region_config.sparse_infill_rotate_template.value); region_config.sparse_infill_rotate_template.value);
@@ -1354,6 +1373,7 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
f->angle = surface_fill.params.angle; f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle; f->fixed_angle = surface_fill.params.fixed_angle;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr; const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->tpms_radial_field = this->object()->tpms_radial_field(surface_fill.params.tpms_adaptive);
f->print_config = &this->object()->print()->config(); f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config(); f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipConcentricInternal) { if (surface_fill.params.pattern == ipConcentricInternal) {
@@ -1403,6 +1423,9 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2; params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle; params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.gyroid_optimized = surface_fill.params.gyroid_optimized; params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.tpms_adaptive = surface_fill.params.tpms_adaptive;
params.tpms_interior_density = float(0.01 * surface_fill.params.tpms_interior_density);
params.tpms_adaptive_gradient = surface_fill.params.tpms_adaptive_gradient;
params.smooth_factor = surface_fill.params.smooth_factor; params.smooth_factor = surface_fill.params.smooth_factor;
// BBS // BBS
@@ -1575,6 +1598,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
f->angle = surface_fill.params.angle; f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle; f->fixed_angle = surface_fill.params.fixed_angle;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr; const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->tpms_radial_field = this->object()->tpms_radial_field(surface_fill.params.tpms_adaptive);
f->print_config = &this->object()->print()->config(); f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config(); f->print_object_config = &this->object()->config();
@@ -1614,6 +1638,9 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle; params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.multiline = surface_fill.params.multiline; params.multiline = surface_fill.params.multiline;
params.gyroid_optimized = surface_fill.params.gyroid_optimized; params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.tpms_adaptive = surface_fill.params.tpms_adaptive;
params.tpms_interior_density = float(0.01 * surface_fill.params.tpms_interior_density);
params.tpms_adaptive_gradient = surface_fill.params.tpms_adaptive_gradient;
params.smooth_factor = surface_fill.params.smooth_factor; params.smooth_factor = surface_fill.params.smooth_factor;
// Orca: Match make_fills() when choosing the origin of plane-path patterns. // Orca: Match make_fills() when choosing the origin of plane-path patterns.
// Without the sparse extrusion role, the filler uses each surface's bounds // Without the sparse extrusion role, the filler uses each surface's bounds
+9
View File
@@ -33,6 +33,7 @@ namespace Slic3r { class ExtrusionEntityCollection; }
namespace Slic3r { namespace Slic3r {
class Surface; class Surface;
class TpmsRadialField;
enum InfillPattern : int; enum InfillPattern : int;
namespace FillAdaptive { namespace FillAdaptive {
@@ -91,6 +92,11 @@ struct FillParams
// For Gyroid: when true, use the parameterized "optimized" variant. // For Gyroid: when true, use the parameterized "optimized" variant.
bool gyroid_optimized { false }; bool gyroid_optimized { false };
// For TPMS: grade the density from the surface to the interior of the object. Density fraction.
TpmsAdaptiveMode tpms_adaptive { TpmsAdaptiveMode::Disabled };
float tpms_interior_density { 0.f };
TpmsAdaptiveGradient tpms_adaptive_gradient { TpmsAdaptiveGradient::Linear };
// Orca: corner smoothing factor in the range [0, 1]. // Orca: corner smoothing factor in the range [0, 1].
double smooth_factor { 0. }; double smooth_factor { 0. };
@@ -155,6 +161,9 @@ public:
// Octree builds on mesh for usage in the adaptive cubic infill // Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr; FillAdaptive::Octree* adapt_fill_octree = nullptr;
// Radial coordinate inside the object for the adaptive TPMS infill
const TpmsRadialField* tpms_radial_field = nullptr;
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring). // PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
// Orca: also used by gap fill function. // Orca: also used by gap fill function.
const PrintConfig *print_config = nullptr; const PrintConfig *print_config = nullptr;
+26 -5
View File
@@ -17,6 +17,17 @@
#include "libslic3r/Polyline.hpp" #include "libslic3r/Polyline.hpp"
#include "FillGyroid.hpp" #include "FillGyroid.hpp"
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "FillTpmsAdaptive.hpp"
namespace Slic3r {
static float gyroid(float x, float y, float z)
{
return std::sin(x) * std::cos(y) + std::sin(y) * std::cos(z) + std::sin(z) * std::cos(x);
}
} // namespace Slic3r
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Marching-squares scalar field for the optimized gyroid branch. // Marching-squares scalar field for the optimized gyroid branch.
@@ -62,10 +73,7 @@ struct GyroidField
float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const
{ {
const float a = fx * float(x); return gyroid(fx * float(x), fy * float(y), fz * float(z_arg));
const float b = fy * float(y);
const float c = fz * float(z_arg);
return std::sin(a) * std::cos(b) + std::sin(b) * std::cos(c) + std::sin(c) * std::cos(a);
} }
float get_scalar(Coord p) const float get_scalar(Coord p) const
@@ -307,6 +315,14 @@ void FillGyroid::_fill_surface_single(
ExPolygon expolygon, ExPolygon expolygon,
Polylines &polylines_out) Polylines &polylines_out)
{ {
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
[&](const FillParams &shell_params, const ExPolygon &shell) {
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
});
return;
}
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.); auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
if(std::abs(infill_angle) >= EPSILON) if(std::abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle); expolygon.rotate(-infill_angle);
@@ -326,7 +342,12 @@ void FillGyroid::_fill_surface_single(
// generate pattern // generate pattern
Polylines polylines; Polylines polylines;
if (params.gyroid_optimized) { if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
// Radians per mm of the regular pattern at a density.
auto frequency = [&params, this](double density) { return density * DensityAdjust / (params.multiline * this->spacing); };
polylines = make_adaptive_tpms({gyroid, frequency(params.density), frequency(params.tpms_interior_density), params.tpms_adaptive_gradient},
*this->tpms_radial_field, bb, this->z, params.layer_height, this->spacing, infill_angle);
} else if (params.gyroid_optimized) {
// Marching-squares path on the gyroid implicit field. Base period matches // Marching-squares path on the gyroid implicit field. Base period matches
// the standard parametric path's wavelength: 2*pi * spacing / density_adj. // the standard parametric path's wavelength: 2*pi * spacing / density_adj.
// omega >= 1 always, so fz >= baseline -> shorter vertical wavelength -> // omega >= 1 always, so fz >= baseline -> shorter vertical wavelength ->
+823
View File
@@ -0,0 +1,823 @@
#include "FillTpmsAdaptive.hpp"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <deque>
#include <functional>
#include <limits>
#include <utility>
#include <vector>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include "../BoundingBox.hpp"
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "FillBase.hpp"
#include "../Execution/ExecutionTBB.hpp"
#include "../MarchingSquares.hpp"
#include "../Point.hpp"
#include "../Polygon.hpp"
#include "../Polyline.hpp"
#include "../PrintConfig.hpp"
#include "../libslic3r.h"
namespace Slic3r {
namespace {
// At most 4 MB of body indices; finer cells would not change the grading.
constexpr double MaxNodes = double(1 << 20);
constexpr double MinCellSize = 0.5;
// Two deepest points are in separate lobes when the depth between them drops below this ratio of the shallower one.
constexpr double NeckRatio = 0.8;
// Lobes shallower than this ratio of the deepest one of their body are graded as part of it.
constexpr double MinLobeRatio = 0.3;
// A lobe reaches twice as far as the side towards its neighbour, so that the side is half way to the surface.
constexpr double LobeReach = 2.;
// Width of the morph between the patterns of two lobes, in their distance to the center over its depth.
constexpr double LobeMorph = 0.1;
// Densities of the levels of Stepped shells, and of Smooth blend, are at most these ratios apart. Fewer levels blend
// with fewer lines running along the blends.
constexpr double ShellRatio = 1.5;
constexpr double BlendRatio = 2.5;
// Distance warp: samples of the mean depth along a ray.
constexpr int Samples = 32;
constexpr float InfF = std::numeric_limits<float>::infinity();
constexpr double InfD = std::numeric_limits<double>::infinity();
// Squared distance transform of a line (Felzenszwalb & Huttenlocher); infinite samples are no sites.
void distance_transform_line(const float *f, float *d, int n, int *v, double *s)
{
int k = -1;
for (int q = 0; q < n; ++q) {
if (f[q] == InfF)
continue;
double x = -InfD;
while (k >= 0) {
x = (f[q] + double(q) * q - f[v[k]] - double(v[k]) * v[k]) / (2. * (q - v[k]));
if (x > s[k])
break;
--k;
}
if (k < 0)
x = -InfD;
v[++k] = q;
s[k] = x;
s[k + 1] = InfD;
}
if (k < 0) {
std::fill(d, d + n, InfF);
return;
}
for (int q = 0, j = 0; q < n; ++q) {
while (s[j + 1] < q)
++j;
d[q] = float(sqr(double(q - v[j])) + f[v[j]]);
}
}
void distance_transform_axis(std::vector<float> &grid, const Vec3i32 &size, int axis, const std::function<void()> &throw_if_canceled)
{
const int n = size[axis];
const int a1 = (axis + 1) % 3;
const int a2 = (axis + 2) % 3;
const size_t stride = axis == 0 ? 1 : axis == 1 ? size_t(size.x()) : size_t(size.x()) * size.y();
tbb::parallel_for(tbb::blocked_range<size_t>(0, size_t(size[a1]) * size[a2]), [&](const tbb::blocked_range<size_t> &range) {
std::vector<float> f(n), d(n);
std::vector<int> v(n);
std::vector<double> s(n + 1);
for (size_t line = range.begin(); line < range.end(); ++line) {
Vec3i32 idx;
idx[axis] = 0;
idx[a1] = int(line % size[a1]);
idx[a2] = int(line / size[a1]);
const size_t first = (size_t(idx.z()) * size.y() + idx.y()) * size.x() + idx.x();
for (int i = 0; i < n; ++i)
f[i] = grid[first + i * stride];
distance_transform_line(f.data(), d.data(), n, v.data(), s.data());
for (int i = 0; i < n; ++i)
grid[first + i * stride] = d[i];
}
throw_if_canceled();
});
}
// Marks the nodes inside the expolygons with infinity, by even-odd scanlines.
void rasterize(const ExPolygons &expolygons, const Vec2d &origin, double cell, int nx, int ny, float *nodes)
{
std::vector<std::vector<double>> crossings(ny);
auto add_crossings = [&](const Polygon &polygon) {
const Points &pts = polygon.points;
for (size_t i = 0; i < pts.size(); ++i) {
const Vec2d a = unscaled(pts[i]);
const Vec2d b = unscaled(pts[i + 1 == pts.size() ? 0 : i + 1]);
if (a.y() == b.y())
continue;
const auto [lo, hi] = std::minmax(a.y(), b.y());
const int j0 = std::max(0, int(std::ceil((lo - origin.y()) / cell)));
const int j1 = std::min(ny, int(std::ceil((hi - origin.y()) / cell)));
for (int j = j0; j < j1; ++j) {
const double y = origin.y() + j * cell;
crossings[j].push_back(a.x() + (b.x() - a.x()) * (y - a.y()) / (b.y() - a.y()));
}
}
};
for (const ExPolygon &expolygon : expolygons) {
add_crossings(expolygon.contour);
for (const Polygon &hole : expolygon.holes)
add_crossings(hole);
}
for (int j = 0; j < ny; ++j) {
std::vector<double> &xs = crossings[j];
std::sort(xs.begin(), xs.end());
for (size_t k = 0; k + 1 < xs.size(); k += 2) {
const int i0 = std::max(0, int(std::ceil((xs[k] - origin.x()) / cell)));
const int i1 = std::min(nx, int(std::ceil((xs[k + 1] - origin.x()) / cell)));
std::fill(nodes + size_t(j) * nx + std::min(i0, i1), nodes + size_t(j) * nx + i1, InfF);
}
}
}
// Scale of the pattern relative to the surface at a depth from 0 at the surface to 1 at the deepest point.
double target_scale(double ratio, TpmsAdaptiveGradient gradient, double depth)
{
switch (gradient) {
case TpmsAdaptiveGradient::Quadratic: return 1. + (ratio - 1.) * depth * depth;
case TpmsAdaptiveGradient::Exponential: return std::pow(ratio, depth);
default: return 1. + (ratio - 1.) * depth;
}
}
// Levels of Stepped shells and Smooth blend, geometric from the surface at 0 to the interior at count, and the
// continuous level of the target of a depth.
struct DensityLevels
{
DensityLevels(double ratio, double step, TpmsAdaptiveGradient gradient)
: ratio(ratio), gradient(gradient), count(int(std::ceil(std::abs(std::log(ratio)) / std::log(step) - EPSILON)))
{}
double scale(int level) const { return count == 0 ? 1. : std::pow(ratio, double(level) / count); }
double level(double depth) const
{
return count == 0 ? 0. : count * std::log(target_scale(ratio, gradient, depth)) / std::log(ratio);
}
double ratio;
TpmsAdaptiveGradient gradient;
int count;
};
// Scale of the pattern around the center at a radial coordinate t. The mean cell scale over the ball of radius t,
// t^-3 * integral of 3 t'^2 * target(t'), or over the disc in 2D, follows the gradient; beyond the surface the target
// is the surface scale.
class RadialScale
{
public:
RadialScale(const AdaptiveTpms &tpms, int dimensions) : m_dimensions(dimensions)
{
const double ratio = std::max(tpms.interior_frequency / tpms.surface_frequency, 1e-3);
auto target = [&tpms, ratio](double depth) { return target_scale(ratio, tpms.gradient, depth); };
m_scale[0] = target(1.);
double volume = 0.;
for (size_t i = 1; i < m_scale.size(); ++i) {
const double t0 = double(i - 1) / double(m_scale.size() - 1);
const double t1 = double(i) / double(m_scale.size() - 1);
volume += (std::pow(t1, m_dimensions) - std::pow(t0, m_dimensions)) * target(1. - 0.5 * (t0 + t1));
m_scale[i] = volume / std::pow(t1, m_dimensions);
}
}
double operator()(double t) const
{
if (t >= 1.) {
const double volume = std::pow(t, m_dimensions);
return (m_scale.back() + volume - 1.) / volume;
}
const double x = t * double(m_scale.size() - 1);
const size_t i = std::min(size_t(x), m_scale.size() - 2);
return m_scale[i] + (m_scale[i + 1] - m_scale[i]) * (x - double(i));
}
private:
int m_dimensions;
std::array<double, 257> m_scale;
};
} // namespace
TpmsRadialField::TpmsRadialField(const std::vector<Slice> &slices, const BoundingBox &bbox, TpmsAdaptiveMode mode,
const std::function<void()> &throw_if_canceled)
: m_mode(mode)
, m_axis(mode == TpmsAdaptiveMode::NormalX ? 0 : mode == TpmsAdaptiveMode::NormalY ? 1 : mode == TpmsAdaptiveMode::NormalZ ? 2 : -1)
{
assert(!slices.empty() && mode != TpmsAdaptiveMode::Disabled);
const Vec3d min(unscaled(bbox.min.x()), unscaled(bbox.min.y()), slices.front().bottom_z);
const Vec3d extent = Vec3d(unscaled(bbox.max.x()), unscaled(bbox.max.y()), slices.back().top_z) - min;
// Padded by a node on each side, so that the border of the grid is outside.
m_cell = std::max(MinCellSize, std::cbrt(extent.prod() / MaxNodes));
auto nodes = [this](double length) { return int(std::ceil(length / m_cell)) + 3; };
while (double(nodes(extent.x())) * nodes(extent.y()) * nodes(extent.z()) > MaxNodes)
m_cell *= 1.1;
m_size = Vec3i32(nodes(extent.x()), nodes(extent.y()), nodes(extent.z()));
m_origin = min - Vec3d::Constant(m_cell);
const size_t sy = size_t(m_size.x());
const size_t sz = sy * m_size.y();
std::vector<float> depth(sz * m_size.z(), 0.f);
tbb::parallel_for(tbb::blocked_range<int>(0, m_size.z()), [&](const tbb::blocked_range<int> &range) {
for (int k = range.begin(); k < range.end(); ++k) {
const double z = m_origin.z() + k * m_cell;
auto it = std::lower_bound(slices.begin(), slices.end(), z, [](const Slice &s, double z) { return s.top_z < z; });
if (it != slices.end() && z > it->bottom_z)
rasterize(*it->expolygons, m_origin.head<2>(), m_cell, m_size.x(), m_size.y(), depth.data() + k * sz);
}
throw_if_canceled();
});
for (int axis = 0; axis < 3; ++axis)
if (axis != m_axis)
distance_transform_axis(depth, m_size, axis, throw_if_canceled);
auto position = [this, sy, sz](size_t i) {
return Vec3d(m_origin + m_cell * Vec3d(double(i % sy), double(i / sy % m_size.y()), double(i / sz)));
};
const std::array<std::ptrdiff_t, 6> steps{1, -1, std::ptrdiff_t(sy), -std::ptrdiff_t(sy), std::ptrdiff_t(sz), -std::ptrdiff_t(sz)};
auto node_of = [this, sy, sz](const Vec3d &pt) -> std::ptrdiff_t {
const Vec3d f = (pt - m_origin) / m_cell;
const long x = std::lround(f.x()), y = std::lround(f.y()), z = std::lround(f.z());
if (x < 0 || y < 0 || z < 0 || x >= m_size.x() || y >= m_size.y() || z >= m_size.z())
return -1;
return std::ptrdiff_t(size_t(z) * sz + size_t(y) * sy + size_t(x));
};
// In the 2D modes, the steps within a section.
auto in_section = [this](size_t step) { return int(step / 2) != m_axis; };
std::vector<std::ptrdiff_t> neighbours;
for (int dz = -1; dz <= 1; ++dz)
for (int dy = -1; dy <= 1; ++dy)
for (int dx = -1; dx <= 1; ++dx)
if ((dx != 0 || dy != 0 || dz != 0) && (m_axis < 0 || Vec3i32(dx, dy, dz)[m_axis] == 0))
neighbours.push_back(std::ptrdiff_t(dz) * std::ptrdiff_t(sz) + std::ptrdiff_t(dy) * std::ptrdiff_t(sy) + dx);
// Bodies are the connected inside nodes, none of which is on the border. The deepest nodes of a body are the
// centers of its lobes, unless the depth between them stays above NeckRatio; where the depth ties, the center
// is the node nearest to the middle of the tied nodes.
const bool lobes = m_mode != TpmsAdaptiveMode::SteppedShells && m_mode != TpmsAdaptiveMode::SmoothBlend;
m_body.assign(depth.size(), -1);
std::vector<size_t> body_nodes;
for (size_t seed = 0; seed < depth.size(); ++seed) {
if (depth[seed] == 0.f || m_body[seed] >= 0)
continue;
const int id = int(m_bodies.size());
body_nodes.assign(1, seed);
m_body[seed] = id;
float max_depth = 0.f;
for (size_t k = 0; k < body_nodes.size(); ++k) {
max_depth = std::max(max_depth, depth[body_nodes[k]]);
for (size_t s = 0; s < steps.size(); ++s)
if (const size_t j = body_nodes[k] + steps[s]; in_section(s) && depth[j] > 0.f && m_body[j] < 0) {
m_body[j] = id;
body_nodes.push_back(j);
}
}
m_bodies.push_back({m_lobes.size(), 0, (std::sqrt(double(max_depth)) - 0.5) * m_cell});
if (!lobes)
continue;
std::vector<size_t> peaks;
for (size_t i : body_nodes)
if (depth[i] >= sqr(MinLobeRatio) * max_depth &&
std::all_of(neighbours.begin(), neighbours.end(), [&](std::ptrdiff_t n) { return depth[i + n] <= depth[i]; }))
peaks.push_back(i);
std::sort(peaks.begin(), peaks.end(), [&depth](size_t a, size_t b) { return depth[a] > depth[b] || (depth[a] == depth[b] && a < b); });
auto necked = [&](size_t a, size_t b) {
const Vec3d pa = position(a), pb = position(b);
const double limit = sqr(NeckRatio) * std::min(depth[a], depth[b]);
const int samples = int(std::ceil((pb - pa).norm() / (0.5 * m_cell)));
for (int s = 1; s < samples; ++s)
if (depth[node_of(pa + (pb - pa) * (double(s) / samples))] < limit)
return true;
return false;
};
// A peak joins the first lobe it sees without a neck, if it is as deep. The lobes are made one at a time,
// from the first remaining peak, testing the others in parallel.
std::vector<std::vector<size_t>> ties;
while (!peaks.empty()) {
const size_t front = peaks.front();
std::vector<char> joins(peaks.size(), 0);
tbb::parallel_for(tbb::blocked_range<size_t>(1, peaks.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t k = range.begin(); k < range.end(); ++k)
if (!necked(front, peaks[k]))
joins[k] = std::sqrt(depth[peaks[k]]) >= std::sqrt(depth[front]) - 1.f ? 1 : 2;
});
std::vector<size_t> &tied = ties.emplace_back(1, front);
std::vector<size_t> remaining;
for (size_t k = 1; k < peaks.size(); ++k)
if (joins[k] == 0)
remaining.push_back(peaks[k]);
else if (joins[k] == 1)
tied.push_back(peaks[k]);
peaks = std::move(remaining);
}
m_bodies.back().lobes = ties.size();
for (const std::vector<size_t> &tied : ties) {
Vec3d middle(0., 0., 0.);
for (size_t i : tied)
middle += position(i);
middle /= double(tied.size());
Vec3d center = position(tied.front());
for (size_t i : tied)
if ((position(i) - middle).squaredNorm() < (center - middle).squaredNorm())
center = position(i);
m_lobes.push_back({center, (std::sqrt(double(depth[tied.front()])) - 0.5) * m_cell, {}});
}
}
throw_if_canceled();
if (m_mode == TpmsAdaptiveMode::SteppedShells || m_mode == TpmsAdaptiveMode::SmoothBlend || m_mode == TpmsAdaptiveMode::DistanceWarp) {
m_depth.assign(depth.size(), 0.f);
for (size_t i = 0; i < depth.size(); ++i)
if (m_body[i] >= 0)
m_depth[i] = float(std::min(1., std::max(0., std::sqrt(double(depth[i])) - 0.5) * m_cell / m_bodies[m_body[i]].depth));
}
// The reach of a lobe is the first exit along each direction from its center, smoothed over the directions.
// It is shortened towards a neighbouring lobe, from where the point is nearer to the other lobe relative to their depths.
std::vector<int> lobe_body(m_lobes.size());
for (size_t id = 0; id < m_bodies.size(); ++id)
std::fill_n(lobe_body.begin() + m_bodies[id].first_lobe, m_bodies[id].lobes, int(id));
const int rows = this->directions() / Azimuth;
auto direction = [this](int i, int j) {
const double azimuth = j * 2. * PI / Azimuth;
Vec3d dir = Vec3d::Zero();
if (m_axis < 0) {
const double polar = (i + 0.5) * PI / Polar;
dir = Vec3d(std::sin(polar) * std::cos(azimuth), std::sin(polar) * std::sin(azimuth), std::cos(polar));
} else {
dir[(m_axis + 1) % 3] = std::cos(azimuth);
dir[(m_axis + 2) % 3] = std::sin(azimuth);
}
return dir;
};
// Two passes of a box filter over the neighbouring directions, for each of the values of a direction.
auto smooth = [rows](std::vector<double> &values, size_t count) {
for (int pass = 0; pass < 2; ++pass) {
std::vector<double> smoothed(values.size(), 0.);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < Azimuth; ++j)
for (size_t k = 0; k < count; ++k) {
double &sum = smoothed[size_t(i * Azimuth + j) * count + k];
for (int di = -1; di <= 1; ++di)
for (int dj = -1; dj <= 1; ++dj)
sum += values[size_t(std::clamp(i + di, 0, rows - 1) * Azimuth + (j + dj + Azimuth) % Azimuth) * count + k];
sum /= 9.;
}
values = std::move(smoothed);
}
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_lobes.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t l = range.begin(); l < range.end(); ++l) {
const int id = lobe_body[l];
const Body &body = m_bodies[id];
Lobe &lobe = m_lobes[l];
// The other lobes of the body, by the distance from the center beyond which they may be nearer.
std::vector<std::pair<double, size_t>> others;
for (size_t k = body.first_lobe; k < body.first_lobe + body.lobes; ++k)
if (k != l)
others.emplace_back((m_lobes[k].center - lobe.center).norm() / (1. + m_lobes[k].depth / lobe.depth), k);
std::sort(others.begin(), others.end());
auto nearer_lobe = [&](const Vec3d &pt, double r) {
for (auto it = others.begin(); it != others.end() && it->first < r; ++it)
if ((pt - m_lobes[it->second].center).norm() / m_lobes[it->second].depth < r / lobe.depth)
return true;
return false;
};
const double step = 0.5 * m_cell;
std::vector<double> log_reach(this->directions());
for (int i = 0; i < rows; ++i)
for (int j = 0; j < Azimuth; ++j) {
const Vec3d dir = direction(i, j);
double r = 0.;
double limit = InfD;
for (;;) {
const Vec3d pt = lobe.center + (r + step) * dir;
const std::ptrdiff_t n = node_of(pt);
if (n < 0 || depth[n] == 0.f || m_body[n] != id || r + step >= limit)
break;
if (limit == InfD && nearer_lobe(pt, r + step))
limit = LobeReach * (r + step);
r += step;
}
log_reach[i * Azimuth + j] = std::log(std::min(r + 0.5 * step, limit));
}
smooth(log_reach, 1);
lobe.reach.resize(log_reach.size());
std::transform(log_reach.begin(), log_reach.end(), lobe.reach.begin(), [](double v) { return float(std::exp(v)); });
if (m_mode == TpmsAdaptiveMode::DistanceWarp) {
std::vector<double> mean(log_reach.size() * (Samples + 1));
for (size_t d = 0; d < log_reach.size(); ++d) {
const Vec3d dir = direction(int(d) / Azimuth, int(d) % Azimuth);
double integral = 0.;
double previous = this->depth(lobe.center);
mean[d * (Samples + 1)] = previous;
for (int k = 1; k <= Samples; ++k) {
// Exact for a depth linear between the samples, a + b * tau, weighted by tau^2.
const double t0 = double(k - 1) / Samples;
const double t1 = double(k) / Samples;
const double d1 = this->depth(lobe.center + t1 * lobe.reach[d] * dir);
const double b = (d1 - previous) * Samples;
const double a = previous - b * t0;
integral += a * (std::pow(t1, 3) - std::pow(t0, 3)) / 3. + b * (std::pow(t1, 4) - std::pow(t0, 4)) / 4.;
previous = d1;
mean[d * (Samples + 1) + k] = 3. * integral / std::pow(t1, 3);
}
}
// Smoothed like the reach: sharper profiles shear the pattern across the layer, adding lines.
smooth(mean, Samples + 1);
lobe.mean_depth.assign(mean.begin(), mean.end());
}
}
throw_if_canceled();
});
// Every other node belongs to its nearest body, within its section in the 2D modes.
if (m_axis >= 0) {
std::vector<bool> has_body(m_size[m_axis], false);
for (size_t i = 0; i < m_body.size(); ++i)
if (m_body[i] >= 0)
has_body[m_axis == 0 ? i % sy : m_axis == 1 ? i / sy % m_size.y() : i / sz] = true;
m_section.assign(m_size[m_axis], -1);
for (int k = 0; k < m_size[m_axis]; ++k)
for (int d = 0; d < m_size[m_axis] && m_section[k] < 0; ++d)
if (k - d >= 0 && has_body[k - d])
m_section[k] = k - d;
else if (k + d < m_size[m_axis] && has_body[k + d])
m_section[k] = k + d;
}
// Stepped shells and Smooth blend only look up the depth.
if (!lobes) {
m_body = {};
return;
}
if (m_bodies.size() == 1) {
std::fill(m_body.begin(), m_body.end(), 0);
return;
}
std::deque<size_t> queue;
for (size_t i = 0; i < m_body.size(); ++i)
if (m_body[i] >= 0)
queue.push_back(i);
while (!queue.empty()) {
const size_t i = queue.front();
queue.pop_front();
const size_t x = i % sy, y = i / sy % m_size.y(), z = i / sz;
const std::array<bool, 6> valid{x + 1 < sy, x > 0, y + 1 < size_t(m_size.y()), y > 0, z + 1 < size_t(m_size.z()), z > 0};
for (size_t k = 0; k < steps.size(); ++k)
if (valid[k] && in_section(k) && m_body[i + steps[k]] < 0) {
m_body[i + steps[k]] = m_body[i];
queue.push_back(i + steps[k]);
}
}
}
double TpmsRadialField::depth(const Vec3d &pt) const
{
assert(!m_depth.empty());
const Vec3d f = (pt - m_origin) / m_cell;
std::array<int, 3> n0;
std::array<double, 3> w;
for (int a = 0; a < 3; ++a) {
n0[a] = std::clamp(int(std::floor(f[a])), 0, m_size[a] - 2);
w[a] = std::clamp(f[a] - n0[a], 0., 1.);
}
const size_t sy = size_t(m_size.x());
const size_t sz = sy * size_t(m_size.y());
double value = 0.;
for (int c = 0; c < 8; ++c) {
const size_t n = size_t(n0[2] + (c >> 2)) * sz + size_t(n0[1] + (c >> 1 & 1)) * sy + size_t(n0[0] + (c & 1));
value += (c & 1 ? w[0] : 1. - w[0]) * (c >> 1 & 1 ? w[1] : 1. - w[1]) * (c >> 2 ? w[2] : 1. - w[2]) * m_depth[n];
}
return value;
}
Vec3d TpmsRadialField::offset(const Vec3d &pt, const Vec3d &center) const
{
Vec3d d = pt - center;
if (m_axis >= 0)
d[m_axis] = 0.;
return d;
}
double TpmsRadialField::radial(const Lobe &lobe, const Vec3d &pt) const
{
const Vec3d d = this->offset(pt, lobe.center);
const double r = d.norm();
// Distance warp: the radial coordinate of the linear profile with the same mean depth, which is 1 - 3/4 of it.
auto warp = [](double mean_depth) { return std::max(0., 4. / 3. * (1. - mean_depth)); };
if (r < EPSILON)
return lobe.mean_depth.empty() ? 0. : warp(lobe.mean_depth.front());
int i = 0;
double fi = 0.;
double azimuth;
if (m_axis < 0) {
const double polar = std::clamp(std::acos(std::clamp(d.z() / r, -1., 1.)) / PI * Polar - 0.5, 0., double(Polar - 1));
i = std::min(int(polar), Polar - 2);
fi = polar - i;
azimuth = std::atan2(d.y(), d.x());
} else
azimuth = std::atan2(d[(m_axis + 2) % 3], d[(m_axis + 1) % 3]);
azimuth *= Azimuth / (2. * PI);
if (azimuth < 0.)
azimuth += Azimuth;
const int j0 = int(azimuth) % Azimuth;
const int j1 = (j0 + 1) % Azimuth;
const double fj = azimuth - std::floor(azimuth);
auto at = [&lobe](int i, int j) { return double(lobe.reach[i * Azimuth + j]); };
double reach = at(i, j0) * (1. - fj) + at(i, j1) * fj;
if (fi > 0.)
reach = reach * (1. - fi) + (at(i + 1, j0) * (1. - fj) + at(i + 1, j1) * fj) * fi;
const double tau = r / reach;
if (lobe.mean_depth.empty())
return tau;
// Beyond the surface, the depth is zero.
auto mean_at = [&lobe, tau](int i, int j) {
const float *mean = lobe.mean_depth.data() + size_t(i * Azimuth + j) * (Samples + 1);
if (tau >= 1.)
return double(mean[Samples]) / (tau * tau * tau);
const double x = tau * Samples;
const int k = std::min(int(x), Samples - 1);
return mean[k] + (mean[k + 1] - mean[k]) * (x - k);
};
double mean = mean_at(i, j0) * (1. - fj) + mean_at(i, j1) * fj;
if (fi > 0.)
mean = mean * (1. - fi) + (mean_at(i + 1, j0) * (1. - fj) + mean_at(i + 1, j1) * fj) * fi;
return warp(mean);
}
size_t TpmsRadialField::radial(const Vec3d &pt, Radials &out) const
{
assert(!this->empty());
Vec3i32 idx;
for (int axis = 0; axis < 3; ++axis)
idx[axis] = std::clamp<int>(int(std::lround((pt[axis] - m_origin[axis]) / m_cell)), 0, m_size[axis] - 1);
auto node = [this](const Vec3i32 &idx) { return (size_t(idx.z()) * m_size.y() + idx.y()) * m_size.x() + idx.x(); };
if (m_axis < 0)
return this->body_radial(node(idx), pt, 1.f, out.data());
// The sections around pt, or the nearest ones with a body.
const double f = std::clamp((pt[m_axis] - m_origin[m_axis]) / m_cell, 0., double(m_size[m_axis] - 1));
const int k = std::min(int(f), m_size[m_axis] - 2);
const float w = float(f - k);
size_t count = 0;
if (w < 1.f) {
idx[m_axis] = m_section[k];
count += this->body_radial(node(idx), pt, 1.f - w, out.data());
}
if (w > 0.f) {
idx[m_axis] = m_section[k + 1];
count += this->body_radial(node(idx), pt, w, out.data() + count);
}
return count;
}
size_t TpmsRadialField::body_radial(size_t node, const Vec3d &pt, float weight, Radial *out) const
{
const Body &body = m_bodies[m_body[node]];
if (body.lobes == 1) {
const Lobe &lobe = m_lobes[body.first_lobe];
out[0] = {lobe.center, radial(lobe, pt), weight};
return 1;
}
// The lobes nearest relative to their depth; they morph into each other near the sides where they are as near.
std::array<std::pair<double, size_t>, MaxMorph> nearest;
size_t count = 0;
for (size_t l = body.first_lobe; l < body.first_lobe + body.lobes; ++l) {
const double d = this->offset(pt, m_lobes[l].center).norm() / m_lobes[l].depth;
if (count < MaxMorph)
nearest[count++] = {d, l};
else if (d < nearest.back().first)
nearest.back() = {d, l};
else
continue;
for (size_t k = count - 1; k > 0 && nearest[k].first < nearest[k - 1].first; --k)
std::swap(nearest[k], nearest[k - 1]);
}
std::array<double, MaxMorph> blend;
double total = 0.;
size_t morphs = 0;
for (; morphs < count; ++morphs) {
const double u = 0.5 - (nearest[morphs].first - nearest[0].first) / LobeMorph;
if (u <= 0.)
break;
blend[morphs] = u * u * (3. - 2. * u);
total += blend[morphs];
}
for (size_t k = 0; k < morphs; ++k) {
const Lobe &lobe = m_lobes[nearest[k].second];
out[k] = {lobe.center, radial(lobe, pt), float(weight * blend[k] / total)};
}
return morphs;
}
} // namespace Slic3r
namespace marchsq {
using namespace Slic3r;
struct AdaptiveTpmsField
{
static constexpr float gsizef = 0.40f; // grid cell size in mm (roughly line segment length).
static constexpr float rsizef = 0.004f; // raster pixel size in mm (roughly point accuracy).
const coord_t rsize = scaled(rsizef);
const long gsize = std::lround(gsizef / rsizef);
const AdaptiveTpms &tpms;
const TpmsRadialField &radial_field;
RadialScale scale;
DensityLevels levels;
Point size;
Point offs;
double z;
double cos_angle;
double sin_angle;
AdaptiveTpmsField(const AdaptiveTpms &tpms, const TpmsRadialField &radial_field, const BoundingBox &bbox, coordf_t z, float angle)
: tpms(tpms), radial_field(radial_field), scale(tpms, radial_field.axis() < 0 ? 3 : 2)
, levels(std::max(tpms.interior_frequency / tpms.surface_frequency, 1e-3), BlendRatio, tpms.gradient)
, size(bbox.size()), offs(bbox.min), z(z)
, cos_angle(std::cos(angle)), sin_angle(std::sin(angle))
{}
// The pattern is scaled around the center of the lobe, morphing into the pattern of a neighbouring lobe near the
// side between them. In the 2D modes only within the section, with the interior frequency along the axis.
// The radial field is in the object frame, the fill is rotated by -angle.
float get_scalar(const Coord &p) const
{
const Point pt = to_Point(p);
const double x = unscaled(pt.x());
const double y = unscaled(pt.y());
const Vec3d obj(cos_angle * x - sin_angle * y, sin_angle * x + cos_angle * y, z);
if (radial_field.mode() == TpmsAdaptiveMode::SmoothBlend) {
// The regular patterns of the two levels around the target of the depth, blended by a smoothstep.
auto lattice = [this, x, y](int level) {
const double frequency = tpms.surface_frequency * levels.scale(level);
return tpms.equation(float(frequency * x), float(frequency * y), float(frequency * z));
};
if (levels.count == 0)
return lattice(0);
const double c = std::clamp(levels.level(radial_field.depth(obj)), 0., double(levels.count));
const int k = std::min(int(c), levels.count - 1);
const double u = c - k;
const double w = u * u * (3. - 2. * u);
return float((w < 1. ? (1. - w) * lattice(k) : 0.) + (w > 0. ? w * lattice(k + 1) : 0.));
}
const int axis = radial_field.axis();
TpmsRadialField::Radials radials;
const size_t count = radial_field.radial(obj, radials);
float value = 0.f;
for (size_t i = 0; i < count; ++i) {
const auto &[center, t, weight] = radials[i];
Vec3d q = tpms.surface_frequency * scale(t) * (obj - center);
if (axis >= 0)
q[axis] = tpms.interior_frequency * obj[axis];
value += weight * tpms.equation(float(cos_angle * q.x() + sin_angle * q.y()), float(cos_angle * q.y() - sin_angle * q.x()), float(q.z()));
}
return value;
}
inline coord_t to_coord(long x) const { return x * rsize; }
inline long to_coordr(coord_t x) const { return x / rsize; }
inline Point to_Point(const Coord &p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
};
template<> struct _RasterTraits<AdaptiveTpmsField>
{
using ValueType = float;
static float get(const AdaptiveTpmsField &sf, size_t row, size_t col) { return sf.get_scalar(Coord(long(row), long(col))); }
static size_t rows(const AdaptiveTpmsField &sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const AdaptiveTpmsField &sf) { return sf.to_coordr(sf.size.x()); }
};
// Continuous density level of the depth over a layer, in the object frame.
struct TpmsLevelField
{
static constexpr float gsizef = 0.5f;
static constexpr float rsizef = 0.05f;
const coord_t rsize = scaled(rsizef);
const long gsize = std::lround(gsizef / rsizef);
const TpmsRadialField &field;
const DensityLevels &levels;
Point size;
Point offs;
double z;
TpmsLevelField(const TpmsRadialField &field, const DensityLevels &levels, const BoundingBox &bbox, coordf_t z)
: field(field), levels(levels), size(bbox.size()), offs(bbox.min), z(z)
{}
float get_scalar(const Coord &p) const
{
const Point pt = to_Point(p);
return float(levels.level(field.depth(Vec3d(unscaled(pt.x()), unscaled(pt.y()), z))));
}
inline coord_t to_coord(long x) const { return x * rsize; }
inline long to_coordr(coord_t x) const { return x / rsize; }
inline Point to_Point(const Coord &p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
};
template<> struct _RasterTraits<TpmsLevelField>
{
using ValueType = float;
static float get(const TpmsLevelField &sf, size_t row, size_t col) { return sf.get_scalar(Coord(long(row), long(col))); }
static size_t rows(const TpmsLevelField &sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const TpmsLevelField &sf) { return sf.to_coordr(sf.size.x()); }
};
} // namespace marchsq
namespace Slic3r {
Polylines make_adaptive_tpms(const AdaptiveTpms &tpms, const TpmsRadialField &field, BoundingBox bbox,
coordf_t z, coordf_t layer_height, coordf_t spacing, float angle)
{
// A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region.
const coord_t cell = scaled(marchsq::AdaptiveTpmsField::gsizef);
bbox.offset(cell);
bbox.merge(align_to_grid(bbox.min, Point(cell, cell)));
const marchsq::AdaptiveTpmsField raster(tpms, field, bbox, z - 0.5 * layer_height, angle);
const std::vector<marchsq::Ring> rings = marchsq::execute_with_policy(ex_tbb, raster, 0.f, {raster.gsize, raster.gsize});
// Loops narrower than two lines print as blobs.
const double min_loop_length = scaled(2. * PI * spacing);
Polylines polylines;
polylines.reserve(rings.size());
for (const marchsq::Ring &ring : rings) {
Polyline polyline;
polyline.points.reserve(ring.size() + 1);
for (const marchsq::Coord &crd : ring)
polyline.points.emplace_back(raster.to_Point(crd));
polyline.points.push_back(polyline.points.front());
polyline.simplify(SCALED_SPARSE_INFILL_RESOLUTION);
if (polyline.length() >= min_loop_length)
polylines.push_back(std::move(polyline));
}
return polylines;
}
std::vector<TpmsShell> make_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z,
float surface_density, float interior_density, TpmsAdaptiveGradient gradient)
{
const DensityLevels levels(interior_density / surface_density, ShellRatio, gradient);
if (levels.count == 0)
return {{surface_density, {expolygon}}};
// A fixed sampling grid, so that every region of a layer gets the same shells.
const coord_t cell = scaled(marchsq::TpmsLevelField::gsizef);
BoundingBox bbox = get_extents(expolygon);
bbox.offset(cell);
bbox.merge(align_to_grid(bbox.min, Point(cell, cell)));
const marchsq::TpmsLevelField raster(field, levels, bbox, z);
// Each level takes the part deeper than the middle between it and the previous one.
std::vector<TpmsShell> shells;
ExPolygons remaining{expolygon};
for (int level = 0; level < levels.count && !remaining.empty(); ++level) {
Polygons deeper;
for (const marchsq::Ring &ring : marchsq::execute_with_policy(ex_tbb, raster, float(level + 0.5), {raster.gsize, raster.gsize})) {
Polygon &polygon = deeper.emplace_back();
polygon.points.reserve(ring.size());
for (const marchsq::Coord &crd : ring)
polygon.points.emplace_back(raster.to_Point(crd));
}
ExPolygons inner = intersection_ex(union_ex(deeper), remaining);
shells.push_back({float(surface_density * levels.scale(level)), diff_ex(remaining, inner)});
remaining = std::move(inner);
}
if (!remaining.empty())
shells.push_back({interior_density, std::move(remaining)});
return shells;
}
void fill_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z, const FillParams &params, coordf_t spacing,
const std::function<void(const FillParams &, const ExPolygon &)> &fill_shell)
{
FillParams shell_params = params;
shell_params.tpms_adaptive = TpmsAdaptiveMode::Disabled;
for (const TpmsShell &shell : make_tpms_shells(field, expolygon, z, params.density, params.tpms_interior_density, params.tpms_adaptive_gradient)) {
shell_params.density = shell.density;
for (const ExPolygon &part : offset_ex(shell.expolygons, -float(scale_(0.5 * spacing))))
fill_shell(shell_params, part);
}
}
} // namespace Slic3r
+143
View File
@@ -0,0 +1,143 @@
#pragma once
#include <array>
#include <cstddef>
#include <functional>
#include <memory>
#include <utility>
#include <vector>
#include "../libslic3r.h"
#include "FillBase.hpp"
#include "../BoundingBox.hpp"
#include "../ExPolygon.hpp"
#include "../Point.hpp"
#include "../Polyline.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
// Radial coordinate inside the lobes of the bodies of an object, sampled from its slices: 0 at the center of a
// lobe, 1 at its surface. Lobes are parts of a body separated by a neck, like two spheres united. In the 2D modes,
// every section normal to the axis has its own bodies and lobes, and distances are measured within the section. The
// modes following the distance to the surface use the depth of every point instead; Distance warp also the lobes.
class TpmsRadialField
{
public:
struct Slice
{
coordf_t bottom_z;
coordf_t top_z;
const ExPolygons *expolygons;
};
struct Radial
{
Vec3d center;
double t;
float weight;
};
// Slices sorted by z, in the XY coordinates of the fill and the print Z.
TpmsRadialField(const std::vector<Slice> &slices, const BoundingBox &bbox, TpmsAdaptiveMode mode,
const std::function<void()> &throw_if_canceled);
// Lobes blended near the sides between them, from a body or from each of the two sections around a point.
static constexpr size_t MaxMorph = 4;
using Radials = std::array<Radial, 2 * MaxMorph>;
// Without a body, as when the object is thinner than the grid cells.
bool empty() const { return m_bodies.empty(); }
// Radial coordinates of pt in unscaled coordinates towards the lobe it belongs to, and towards the neighbouring
// lobes near the sides between them, with weights summing to 1. In the 2D modes, those of the two sections around
// pt. Returns their count.
size_t radial(const Vec3d &pt, Radials &out) const;
// Axis normal to the sections in the 2D modes, -1 in the modes graded in 3D.
int axis() const { return m_axis; }
TpmsAdaptiveMode mode() const { return m_mode; }
// In the modes following the distance to the surface: depth relative to the deepest point of the body, from 0 at
// the surface to 1.
double depth(const Vec3d &pt) const;
private:
struct Lobe
{
Vec3d center;
double depth;
// Distance from the center to the surface on a latitude-longitude grid of directions.
std::vector<float> reach;
// Distance warp: mean depth over the ball along each direction, sampled up to the reach.
std::vector<float> mean_depth;
};
struct Body
{
size_t first_lobe;
size_t lobes;
// Distance from the deepest point to the surface.
double depth;
};
double radial(const Lobe &lobe, const Vec3d &pt) const;
size_t body_radial(size_t node, const Vec3d &pt, float weight, Radial *out) const;
// Offset of pt from a center, within the section in the 2D modes.
Vec3d offset(const Vec3d &pt, const Vec3d &center) const;
int directions() const { return m_axis < 0 ? Polar * Azimuth : Azimuth; }
// Directions of the reach of a lobe, on a latitude-longitude grid, or a circle in the 2D modes.
static constexpr int Polar = 24;
static constexpr int Azimuth = 48;
TpmsAdaptiveMode m_mode;
int m_axis;
Vec3d m_origin;
double m_cell;
Vec3i32 m_size;
// Nearest body of every grid node.
std::vector<int> m_body;
std::vector<Body> m_bodies;
std::vector<Lobe> m_lobes;
// In the 2D modes, the nearest section with a body to every section.
std::vector<int> m_section;
// In the modes following the distance to the surface, the depth of every grid node.
std::vector<float> m_depth;
};
using TpmsRadialFieldPtr = std::unique_ptr<TpmsRadialField>;
// A field for every adaptive mode in use, indexed by the mode.
using TpmsRadialFields = std::array<TpmsRadialFieldPtr, size_t(TpmsAdaptiveMode::Count)>;
struct AdaptiveTpms
{
// Implicit TPMS equation with a period of 2 PI.
float (*equation)(float x, float y, float z);
// Pattern frequencies at the surface and at the center, in radians per mm.
double surface_frequency;
double interior_frequency;
TpmsAdaptiveGradient gradient;
};
// Infill lines in the fill frame, the object frame rotated by -angle; z is the print_z of the layer.
Polylines make_adaptive_tpms(const AdaptiveTpms &tpms, const TpmsRadialField &field, BoundingBox bbox,
coordf_t z, coordf_t layer_height, coordf_t spacing, float angle);
struct TpmsShell
{
float density;
ExPolygons expolygons;
};
// Stepped shells: the parts of an expolygon in the object frame at each density, from the surface inwards; z is the
// middle of the layer.
std::vector<TpmsShell> make_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z,
float surface_density, float interior_density, TpmsAdaptiveGradient gradient);
// Stepped shells: fills every shell with fill_shell at its density, each shrunk by half a line like a filled region,
// so the lines connected along the boundaries of two shells don't overlap.
void fill_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z, const FillParams &params, coordf_t spacing,
const std::function<void(const FillParams &, const ExPolygon &)> &fill_shell);
} // namespace Slic3r
+42 -17
View File
@@ -14,8 +14,10 @@
#include "libslic3r/Fill/FillBase.hpp" #include "libslic3r/Fill/FillBase.hpp"
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "FillTpmsD.hpp" #include "FillTpmsD.hpp"
#include "FillTpmsAdaptive.hpp"
namespace Slic3r { namespace Slic3r {
@@ -23,6 +25,11 @@ static double scaled_floor(double x,double scale){
return std::floor(x/scale)*scale; return std::floor(x/scale)*scale;
} }
static float schwarz_d(float x, float y, float z)
{
return std::sin(x) * std::sin(y) * std::sin(z) - std::cos(x) * std::cos(y) * std::cos(z);
}
static Polylines make_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) static Polylines make_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{ {
const double scaleFactor = scale_(line_spacing) / density_adjusted; const double scaleFactor = scale_(line_spacing) / density_adjusted;
@@ -110,30 +117,48 @@ void FillTpmsD::_fill_surface_single(
ExPolygon expolygon, ExPolygon expolygon,
Polylines &polylines_out) Polylines &polylines_out)
{ {
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
[&](const FillParams &shell_params, const ExPolygon &shell) {
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
});
return;
}
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.); auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
if(std::abs(infill_angle) >= EPSILON) if(std::abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle); expolygon.rotate(-infill_angle);
BoundingBox bb = expolygon.contour.bounding_box(); Polylines polylines;
// Density adjusted to have a good %of weight. if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline); // Radians per mm of the regular pattern at a density.
// Distance between the gyroid waves in scaled coordinates. auto frequency = [&params, this](double density) { return density * DensityAdjust / (params.multiline * this->spacing); };
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); BoundingBox bbox = expolygon.contour.bounding_box();
bbox.offset(scale_((params.multiline + 1) * this->spacing));
polylines = make_adaptive_tpms({schwarz_d, frequency(params.density), frequency(params.tpms_interior_density), params.tpms_adaptive_gradient},
*this->tpms_radial_field, bbox, this->z, params.layer_height, this->spacing, infill_angle);
} else {
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// align bounding box to a multiple of our grid module // align bounding box to a multiple of our grid module
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// generate pattern // generate pattern
Polylines polylines = make_waves( polylines = make_waves(
scale_(this->z), scale_(this->z),
density_adjusted, density_adjusted,
this->spacing, this->spacing,
ceil(bb.size()(0) / distance) + 1., ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.); ceil(bb.size()(1) / distance) + 1.);
// shift the polyline to the grid origin // shift the polyline to the grid origin
for (Polyline &pl : polylines) for (Polyline &pl : polylines)
pl.translate(bb.min); pl.translate(bb.min);
}
// Apply multiline offset if needed // Apply multiline offset if needed
multiline_fill(polylines, params, spacing); multiline_fill(polylines, params, spacing);
+33 -15
View File
@@ -8,6 +8,7 @@
#include "libslic3r/Fill/FillBase.hpp" #include "libslic3r/Fill/FillBase.hpp"
#include "libslic3r/ExPolygon.hpp" #include "libslic3r/ExPolygon.hpp"
#include "FillTpmsFK.hpp" #include "FillTpmsFK.hpp"
#include "FillTpmsAdaptive.hpp"
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
@@ -17,6 +18,18 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
// Fischer - Koch S equation:
// cos(2x)sin(y)cos(z) + cos(2y)sin(z)cos(x) + cos(2z)sin(x)cos(y) = 0
static float fischer_koch(float x, float y, float z)
{
return cosf(2 * x) * sinf(y) * cosf(z) + cosf(2 * y) * sinf(z) * cosf(x) + cosf(2 * z) * sinf(x) * cosf(y);
}
} // namespace Slic3r
namespace marchsq { namespace marchsq {
using namespace Slic3r; using namespace Slic3r;
@@ -42,16 +55,7 @@ struct ScalarField
{} {}
// Get the scalar field value at x,y,z in coordf_t coordinates. // Get the scalar field value at x,y,z in coordf_t coordinates.
float get_scalar(coordf_t x, coordf_t y, coordf_t z) const float get_scalar(coordf_t x, coordf_t y, coordf_t z) const { return fischer_koch(freq * x, freq * y, freq * z); }
{
const float fx = freq * x;
const float fy = freq * y;
const float fz = freq * z;
// Fischer - Koch S equation:
// cos(2x)sin(y)cos(z) + cos(2y)sin(z)cos(x) + cos(2z)sin(x)cos(y) = 0
return cosf(2 * fx) * sinf(fy) * cosf(fz) + cosf(2 * fy) * sinf(fz) * cosf(fx) + cosf(2 * fz) * sinf(fx) * cosf(fy);
}
// Get the scalar field value at a Coord for the current z value. // Get the scalar field value at a Coord for the current z value.
float get_scalar(Coord p) const float get_scalar(Coord p) const
@@ -128,20 +132,34 @@ void FillTpmsFK::_fill_surface_single(const FillParams& params,
ExPolygon expolygon, ExPolygon expolygon,
Polylines& polylines_out) Polylines& polylines_out)
{ {
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
[&](const FillParams &shell_params, const ExPolygon &shell) {
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
});
return;
}
auto infill_angle = float(this->angle + (CorrectionAngle * 2 * M_PI) / 360.); auto infill_angle = float(this->angle + (CorrectionAngle * 2 * M_PI) / 360.);
if (std::abs(infill_angle) >= EPSILON) if (std::abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle); expolygon.rotate(-infill_angle);
float density_factor = std::min(0.9f, params.density);
// Density (field period) adjusted to have a good %of weight. // Density (field period) adjusted to have a good %of weight.
const float vari_T = 4.18f * spacing * params.multiline / density_factor; auto period = [&params, this](float density) { return 4.18f * spacing * params.multiline / std::min(0.9f, density); };
BoundingBox bbox = expolygon.contour.bounding_box(); BoundingBox bbox = expolygon.contour.bounding_box();
// Enlarge the bounding box by the multi-line width to avoid artifacts at the edges. // Enlarge the bounding box by the multi-line width to avoid artifacts at the edges.
bbox.offset(scale_((params.multiline + 1) * spacing)); bbox.offset(scale_((params.multiline + 1) * spacing));
marchsq::ScalarField sf = marchsq::ScalarField(bbox, this->z, vari_T); Polylines polylines;
// Get simplified lines using coarse tolerance of 0.1mm (this is infill). if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
Polylines polylines = marchsq::get_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION); polylines = make_adaptive_tpms({fischer_koch, 2. * PI / period(params.density), 2. * PI / period(params.tpms_interior_density),
params.tpms_adaptive_gradient},
*this->tpms_radial_field, bbox, this->z, params.layer_height, spacing, infill_angle);
} else {
marchsq::ScalarField sf = marchsq::ScalarField(bbox, this->z, period(params.density));
// Get simplified lines using coarse tolerance of 0.1mm (this is infill).
polylines = marchsq::get_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
}
// Apply multiline offset if needed // Apply multiline offset if needed
multiline_fill(polylines, params, spacing); multiline_fill(polylines, params, spacing);
+7 -2
View File
@@ -314,8 +314,13 @@ void AMFParserContext::startElement(const char *name, const char **atts)
case 2: case 2:
if (strcmp(name, "metadata") == 0) { if (strcmp(name, "metadata") == 0) {
if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) { if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) {
m_value[0] = get_attribute(atts, "type"); const char *type = get_attribute(atts, "type");
node_type_new = NODE_TYPE_METADATA; if (type == nullptr)
this->stop();
else {
m_value[0] = type;
node_type_new = NODE_TYPE_METADATA;
}
} }
}/* else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) }/* else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT)
node_type_new = NODE_TYPE_LAYER_CONFIG;*/ node_type_new = NODE_TYPE_LAYER_CONFIG;*/
+152
View File
@@ -24,6 +24,7 @@
#include "Polygon.hpp" #include "Polygon.hpp"
#include "Polyline.hpp" #include "Polyline.hpp"
#include "PrintBase.hpp" #include "PrintBase.hpp"
#include "ConnectedBodies.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "enum_bitmask.hpp" #include "enum_bitmask.hpp"
#include "libslic3r.h" #include "libslic3r.h"
@@ -2590,6 +2591,156 @@ WipeTowerType GCode::wipe_tower_type()
return WipeTowerType::Type2; return WipeTowerType::Type2;
} }
// Numbers the object instances and the connected bodies of the instances of several, for the processor to find those an
// extrusion lies in.
static void set_mass_locator(GCodeProcessor &processor, const Print &print)
{
struct Object
{
const PrintObject *object;
int first_instance;
// No bodies for an object of one.
size_t bodies_count;
int first_body;
std::vector<coordf_t> print_zs;
// Per layer, the body of each island and a locator whose boxes are widened for walls reaching past them.
std::vector<std::vector<size_t>> bodies;
std::vector<IslandLocator> islands;
// Per instance, whether its widened box reaches another's, so that the box of an island proves nothing.
std::vector<bool> crowded;
};
std::vector<Object> objects;
std::vector<GCodeProcessorResult::ObjectMass> object_masses;
int bodies_total = 0;
for (const PrintObject *object : print.objects()) {
const auto layers = object->layers();
if (layers.empty())
continue;
// Bodies for assemblies only, as the Prepare tab counts them: those separated infills found, if it needed them.
const ModelVolumePtrs &volumes = object->model_object()->volumes;
const bool assembly = std::count_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return v->is_model_part(); }) > 1 ||
std::any_of(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return v->is_negative_volume(); });
size_t count = 0;
std::vector<std::vector<size_t>> bodies;
if (assembly) {
count = object->separated_body_bboxes().size();
if (count > 0 && std::all_of(layers.begin(), layers.end(), [](const Layer *l) { return l->lslices_separated_component_ids.size() == l->lslices.size(); }))
for (const Layer *layer : layers)
bodies.emplace_back(layer->lslices_separated_component_ids);
else {
std::vector<const ExPolygons *> islands;
for (const Layer *layer : layers)
islands.emplace_back(&layer->lslices);
bodies = connected_bodies(islands, count);
}
}
if (count < 2) {
count = 0;
bodies.assign(layers.size(), {});
}
Object &o = objects.emplace_back(Object{ object, int(object_masses.size()), count, bodies_total, {}, std::move(bodies), {}, {} });
object_masses.resize(object_masses.size() + object->instances().size());
for (size_t instance = 0; instance < object->instances().size(); ++instance)
object_masses[o.first_instance + instance].assembly = assembly;
bodies_total += int(count * object->instances().size());
for (const Layer *layer : layers) {
o.print_zs.emplace_back(layer->print_z);
o.islands.emplace_back(layer->lslices, scaled<coord_t>(1.));
}
}
if (objects.empty())
return;
std::vector<BoundingBox> boxes;
for (const Object &o : objects) {
BoundingBox box;
for (const IslandLocator &islands : o.islands)
for (const BoundingBox &island : islands.boxes())
box.merge(island);
for (const PrintInstance &instance : o.object->instances()) {
BoundingBox &moved = boxes.emplace_back(box);
moved.translate(instance.shift);
}
}
for (Object &o : objects)
for (size_t instance = 0; instance < o.object->instances().size(); ++instance) {
const size_t i = o.first_instance + instance;
o.crowded.emplace_back(false);
for (size_t j = 0; j < boxes.size() && !o.crowded.back(); ++j)
o.crowded.back() = j != i && boxes[i].overlap(boxes[j]);
}
struct Hit
{
size_t object{ 0 }, instance{ 0 }, layer{ 0 }, island{ 0 };
};
auto locate = [objects = std::move(objects), footprints = std::move(boxes),
last = std::optional<Hit>()](const Vec3d &point, bool support) mutable -> GCodeProcessor::MassLocation {
// Supports stand below and around their object: the instance whose footprint holds the point, the one whose center
// is nearest among several, else the nearest footprint.
if (support) {
const Point p(scaled(point.x()), scaled(point.y()));
int found = -1;
bool inside = false;
double best = std::numeric_limits<double>::max();
for (size_t i = 0; i < footprints.size(); ++i) {
const BoundingBox &box = footprints[i];
const double gap = Point((box.min - p).cwiseMax(p - box.max).cwiseMax(0)).cast<double>().squaredNorm();
const bool in = gap == 0.;
const double d = in ? (box.center() - p).cast<double>().squaredNorm() : gap;
if ((in && !inside) || (in == inside && d < best)) {
found = int(i);
inside = in;
best = d;
}
}
return { found, -1 };
}
constexpr double z_tolerance = 0.002;
const auto local = [&point, &objects](size_t object, size_t instance) {
return Point(Point(scaled(point.x()), scaled(point.y())) - objects[object].object->instances()[instance].shift);
};
const auto location = [&objects, &last](const Hit &hit) {
last = hit;
const Object &o = objects[hit.object];
return GCodeProcessor::MassLocation{ o.first_instance + int(hit.instance),
o.bodies_count == 0 ? -1 : o.first_body + int(hit.instance * o.bodies_count + o.bodies[hit.layer][hit.island]) };
};
// A point lies on the first layer at or above it, as spiral vase rises through each layer.
// Extrusions mostly follow each other on one island.
if (last) {
const Object &o = objects[last->object];
if (point.z() <= o.print_zs[last->layer] + z_tolerance &&
(last->layer == 0 || point.z() > o.print_zs[last->layer - 1] + z_tolerance) &&
o.islands[last->layer].holds(last->island, local(last->object, last->instance), o.crowded[last->instance]))
return location(*last);
}
// Outside the islands of instances crowding each other, the nearest outline.
std::optional<Hit> nearest;
double distance = std::numeric_limits<double>::max();
for (size_t object = 0; object < objects.size(); ++object) {
const Object &o = objects[object];
const auto z = std::lower_bound(o.print_zs.begin(), o.print_zs.end(), point.z() - z_tolerance);
if (z == o.print_zs.end())
continue;
const size_t layer = size_t(z - o.print_zs.begin());
for (size_t instance = 0; instance < o.object->instances().size(); ++instance) {
const auto [island, d] = o.islands[layer].find(local(object, instance), o.crowded[instance]);
if (island < 0)
continue;
const Hit hit{ object, instance, layer, size_t(island) };
if (d == 0. || !o.crowded[instance])
return location(hit);
if (d < distance) {
distance = d;
nearest = hit;
}
}
}
return nearest ? location(*nearest) : GCodeProcessor::MassLocation{};
};
processor.set_mass_locator(std::move(locate), std::move(object_masses));
}
void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
{ {
PROFILE_CLEAR(); PROFILE_CLEAR();
@@ -3112,6 +3263,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// modifies m_silent_time_estimator_enabled // modifies m_silent_time_estimator_enabled
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
print.get_layered_nozzle_group_result()); print.get_layered_nozzle_group_result());
set_mass_locator(m_processor, print);
const bool is_bbl_printers = print.is_BBL_printer(); const bool is_bbl_printers = print.is_BBL_printer();
const bool skip_config_block = print.config().gcode_skip_config_block; const bool skip_config_block = print.config().gcode_skip_config_block;
const WipeTowerType wipe_tower_type = print.wipe_tower_type(); const WipeTowerType wipe_tower_type = print.wipe_tower_type();
+76 -1
View File
@@ -89,7 +89,6 @@ static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f;
static const size_t MIN_EXTRUDERS_COUNT = 5; static const size_t MIN_EXTRUDERS_COUNT = 5;
static const float DEFAULT_FILAMENT_DIAMETER = 1.75f; static const float DEFAULT_FILAMENT_DIAMETER = 1.75f;
static const int DEFAULT_FILAMENT_HRC = 0; static const int DEFAULT_FILAMENT_HRC = 0;
static const float DEFAULT_FILAMENT_DENSITY = 1.245f;
static const float DEFAULT_FILAMENT_COST = 29.99f; static const float DEFAULT_FILAMENT_COST = 29.99f;
static const int DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE = 0; static const int DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE = 0;
static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
@@ -2604,6 +2603,10 @@ void GCodeProcessorResult::reset() {
lock(); lock();
moves.clear(); moves.clear();
plate_mass = {};
object_masses.clear();
body_masses.clear();
support_masses.clear();
lines_ends.clear(); lines_ends.clear();
printable_area = Pointfs(); printable_area = Pointfs();
//BBS: add bed exclude area //BBS: add bed exclude area
@@ -3702,6 +3705,7 @@ void GCodeProcessor::reset()
m_g1_line_id = 0; m_g1_line_id = 0;
m_layer_id = 0; m_layer_id = 0;
m_cp_color.reset(); m_cp_color.reset();
m_mass_locator = nullptr;
m_producer = EProducer::Unknown; m_producer = EProducer::Unknown;
@@ -3841,6 +3845,7 @@ void GCodeProcessor::process_buffer(const std::string &buffer)
void GCodeProcessor::finalize(bool post_process) void GCodeProcessor::finalize(bool post_process)
{ {
m_result.z_offset = m_z_offset; m_result.z_offset = m_z_offset;
finalize_object_masses();
// update width/height of wipe moves // update width/height of wipe moves
for (GCodeProcessorResult::MoveVertex& move : m_result.moves) { for (GCodeProcessorResult::MoveVertex& move : m_result.moves) {
@@ -5469,6 +5474,9 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset); m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset);
} }
if (type == EMoveType::Extrude)
add_object_mass(filament_id, area_filament_cross_section * delta_pos[E]);
// store move // store move
store_move_vertex(type); store_move_vertex(type);
} }
@@ -7277,6 +7285,73 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
} }
} }
void GCodeProcessorResult::ObjectMass::add(const Sum &sum, const BoundingBoxf3 &extent, size_t layer)
{
box.merge(extent);
if (printed_up_to_layer.size() <= layer)
printed_up_to_layer.resize(layer + 1);
printed_up_to_layer[layer].add(sum);
}
void GCodeProcessor::add_object_mass(int filament_id, float volume)
{
// Skirt, prime tower and custom G-code belong to no object.
const ExtrusionRole role = m_extrusion_role;
if (volume <= 0.f || role == erNone || role == erSkirt || role == erWipeTower || role == erCustom || role == erMixed)
return;
const bool has_density = size_t(filament_id) < m_result.filament_densities.size() && m_result.filament_densities[filament_id] > 0.f;
const double mass = double(volume) * (has_density ? m_result.filament_densities[filament_id] : DEFAULT_FILAMENT_DENSITY);
// In the frame of the stored moves, the bead's center half its height below the nozzle, from the move's start to its end.
const Vec3d half_height = 0.5 * double(m_height) * Vec3d::UnitZ();
const Vec3d offset = Vec3d(m_x_offset, m_y_offset, -m_z_offset) - half_height + m_extruder_offsets[filament_id].cast<double>();
const Vec3d start = Vec3d(m_start_position[X], m_start_position[Y], m_start_position[Z]) + offset;
const Vec3d end = Vec3d(m_end_position[X], m_end_position[Y], m_end_position[Z]) + offset;
// The second moments of a uniform segment.
const GCodeProcessorResult::ObjectMass::Sum sum{ mass, double(volume), 0.5 * mass * (start + end),
mass / 3. * (start.cwiseProduct(start) + start.cwiseProduct(end) + end.cwiseProduct(end)) };
// Of the bead's center line and its height, as its width is only estimated. Merged, as a wall along an axis is flat.
BoundingBoxf3 extent;
extent.merge(start.cwiseMin(end) - half_height);
extent.merge(start.cwiseMax(end) + half_height);
const bool part = role != erBrim && !is_support(role);
const size_t layer = std::max<unsigned int>(1, m_layer_id) - 1;
m_result.plate_mass.add(sum, extent, layer);
// The brim belongs to the plate alone.
if (role == erBrim || !m_mass_locator)
return;
const auto add = [&sum, &extent, layer](std::vector<GCodeProcessorResult::ObjectMass> &masses, int index) {
if (index < 0)
return;
if (masses.size() <= size_t(index))
masses.resize(index + 1);
masses[index].add(sum, extent, layer);
};
// At the nozzle's height, which the layers print at.
const MassLocation location = m_mass_locator(0.5 * (start + end) + half_height, !part);
if (part) {
add(m_result.object_masses, location.object);
add(m_result.body_masses, location.body);
} else
add(m_result.support_masses, location.object);
}
void GCodeProcessor::finalize_object_masses()
{
const auto accumulate = [](GCodeProcessorResult::ObjectMass &object) {
for (size_t i = 1; i < object.printed_up_to_layer.size(); ++i)
object.printed_up_to_layer[i].add(object.printed_up_to_layer[i - 1]);
};
accumulate(m_result.plate_mass);
for (GCodeProcessorResult::ObjectMass &object : m_result.object_masses)
accumulate(object);
for (GCodeProcessorResult::ObjectMass &body : m_result.body_masses)
accumulate(body);
for (GCodeProcessorResult::ObjectMass &support : m_result.support_masses)
accumulate(support);
}
void GCodeProcessor::set_extrusion_role(ExtrusionRole role) void GCodeProcessor::set_extrusion_role(ExtrusionRole role)
{ {
m_used_filaments.process_role_cache(this); m_used_filaments.process_role_cache(this);
+62
View File
@@ -3,6 +3,7 @@
#include "libslic3r/CommonDefs.hpp" #include "libslic3r/CommonDefs.hpp"
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include "libslic3r/ArcFitter.hpp" #include "libslic3r/ArcFitter.hpp"
@@ -35,6 +36,9 @@ namespace Slic3r {
class Print; class Print;
// For a filament whose density is not set, in g/cm³.
inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
// slice warnings enum strings // slice warnings enum strings
#define NOZZLE_HRC_CHECKER "the_actual_nozzle_hrc_smaller_than_the_required_nozzle_hrc" #define NOZZLE_HRC_CHECKER "the_actual_nozzle_hrc_smaller_than_the_required_nozzle_hrc"
#define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament" #define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament"
@@ -270,9 +274,44 @@ class Print;
std::vector<std::string> params; // extra msg info std::vector<std::string> params; // extra msg info
}; };
// Material extruded for the plate, one object instance or one connected body of it, for their centers of mass.
struct ObjectMass
{
struct Sum
{
double mass{ 0. };
double volume{ 0. };
Vec3d moment{ Vec3d::Zero() };
// Of the mass about the origin along each axis, the sums of m x^2, m y^2 and m z^2.
Vec3d second{ Vec3d::Zero() };
void add(const Sum &other)
{
mass += other.mass;
volume += other.volume;
moment += other.moment;
second += other.second;
}
};
// Everything printed up to each layer id, the plate's with brim, raft and supports, and the box it fills.
std::vector<Sum> printed_up_to_layer;
BoundingBoxf3 box;
// Of an object, whether it is an assembly.
bool assembly{ false };
Sum total() const { return printed_up_to_layer.empty() ? Sum{} : printed_up_to_layer.back(); }
void add(const Sum &sum, const BoundingBoxf3 &extent, size_t layer);
};
std::string filename; std::string filename;
unsigned int id; unsigned int id;
std::vector<MoveVertex> moves; std::vector<MoveVertex> moves;
ObjectMass plate_mass;
// One per object instance, and one per connected body of the instances of several, when the sliced objects were at hand.
std::vector<ObjectMass> object_masses;
std::vector<ObjectMass> body_masses;
// One per object instance, of its supports and raft.
std::vector<ObjectMass> support_masses;
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
std::vector<size_t> lines_ends; std::vector<size_t> lines_ends;
Pointfs printable_area; Pointfs printable_area;
@@ -360,6 +399,10 @@ class Print;
filename = std::forward<Other>(other).filename; filename = std::forward<Other>(other).filename;
id = std::forward<Other>(other).id; id = std::forward<Other>(other).id;
moves = std::forward<Other>(other).moves; moves = std::forward<Other>(other).moves;
plate_mass = std::forward<Other>(other).plate_mass;
object_masses = std::forward<Other>(other).object_masses;
body_masses = std::forward<Other>(other).body_masses;
support_masses = std::forward<Other>(other).support_masses;
lines_ends = std::forward<Other>(other).lines_ends; lines_ends = std::forward<Other>(other).lines_ends;
printable_area = std::forward<Other>(other).printable_area; printable_area = std::forward<Other>(other).printable_area;
bed_exclude_area = std::forward<Other>(other).bed_exclude_area; bed_exclude_area = std::forward<Other>(other).bed_exclude_area;
@@ -1099,6 +1142,15 @@ class Print;
}; };
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// The object instance and the connected body of an instance of several that a point lies in, -1 for none.
struct MassLocation
{
int object{ -1 };
int body{ -1 };
};
// For a support, the object instance only.
using MassLocator = std::function<MassLocation(const Vec3d &point, bool support)>;
private: private:
CommandProcessor m_command_processor; CommandProcessor m_command_processor;
GCodeReader m_parser; GCodeReader m_parser;
@@ -1126,6 +1178,7 @@ class Print;
bool m_skippable{false}; bool m_skippable{false};
SkipType m_skippable_type{SkipType::stNone}; SkipType m_skippable_type{SkipType::stNone};
int m_object_label_id{-1}; int m_object_label_id{-1};
MassLocator m_mass_locator;
float m_print_z{0.0f}; float m_print_z{0.0f};
std::vector<float> m_remaining_volume; std::vector<float> m_remaining_volume;
ExtruderTemps m_filament_nozzle_temp; ExtruderTemps m_filament_nozzle_temp;
@@ -1280,6 +1333,13 @@ class Print;
const std::vector<std::set<int>>& unprintable_filament_types ); const std::vector<std::set<int>>& unprintable_filament_types );
void apply_config(const PrintConfig& config); void apply_config(const PrintConfig& config);
void set_print(Print* print) { m_print = print; } void set_print(Print* print) { m_print = print; }
// Locates extrusions in the objects and bodies it numbers, those objects listed beforehand.
void set_mass_locator(MassLocator locator, std::vector<GCodeProcessorResult::ObjectMass> objects)
{
m_mass_locator = std::move(locator);
m_result.support_masses.assign(objects.size(), {});
m_result.object_masses = std::move(objects);
}
// Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the // Hand the nozzle grouping context to the estimator BEFORE the streaming replay, so the
// per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0). // per-slot machine-limit resolution can follow the active nozzle. Null is fine (slot 0).
void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) { void initialize_from_context(const std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase>& nozzle_group_result) {
@@ -1534,6 +1594,8 @@ class Print;
//BBS: different path_type is only used for arc move //BBS: different path_type is only used for arc move
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move, bool internal_only = false); void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move, bool internal_only = false);
void add_object_mass(int filament_id, float volume);
void finalize_object_masses();
void set_extrusion_role(ExtrusionRole role); void set_extrusion_role(ExtrusionRole role);
// Resolve the SKIPPABLE_TYPE payload to a SkipType. // Resolve the SKIPPABLE_TYPE payload to a SkipType.
+6 -2
View File
@@ -437,10 +437,14 @@ coordf_t Layer::get_sparse_infill_max_void_area()
double max_void_area = 0.; double max_void_area = 0.;
for (auto layerm : m_regions) { for (auto layerm : m_regions) {
Flow flow = layerm->flow(frInfill); Flow flow = layerm->flow(frInfill);
float density = layerm->region().config().sparse_infill_density; const PrintRegionConfig &config = layerm->region().config();
InfillPattern pattern = layerm->region().config().sparse_infill_pattern; float density = config.sparse_infill_density;
InfillPattern pattern = config.sparse_infill_pattern;
if (density == 0.) if (density == 0.)
return -1; return -1;
// Orca: the adaptive TPMS infill is as sparse as its interior density.
if (density < 100.f && config.tpms_adaptive != TpmsAdaptiveMode::Disabled && is_tpms_adaptive_pattern(pattern))
density = std::min(density, std::max(1.f, float(config.tpms_interior_density)));
//BBS: rough estimation and need to be optimized //BBS: rough estimation and need to be optimized
double spacing = flow.scaled_spacing() * (100 - density) / density; double spacing = flow.scaled_spacing() * (100 - density) / density;
+11 -2
View File
@@ -1181,6 +1181,9 @@ static std::vector<std::string> s_Preset_print_options{
"is_infill_first", "is_infill_first",
"sparse_infill_density", "sparse_infill_density",
"fill_multiline", "fill_multiline",
"tpms_adaptive",
"tpms_interior_density",
"tpms_adaptive_gradient",
"gyroid_optimized", "gyroid_optimized",
"sparse_infill_pattern", "sparse_infill_pattern",
"sparse_infill_smooth_factor", "sparse_infill_smooth_factor",
@@ -2345,7 +2348,7 @@ bool PresetCollection::reset_project_embedded_presets()
return re_select; return re_select;
} }
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time) void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id)
{ {
lock(); lock();
const std::string canonical_name = this->canonical_preset_name(name); const std::string canonical_name = this->canonical_preset_name(name);
@@ -2363,7 +2366,10 @@ void PresetCollection::set_sync_info_and_save(std::string name, std::string sett
preset2.save_info(); preset2.save_info();
} }
} }
preset->setting_id = setting_id; if (!setting_id.empty())
preset->setting_id = setting_id;
if (!user_id.empty())
preset->user_id = user_id;
if (update_time > 0) if (update_time > 0)
preset->updated_time = update_time; preset->updated_time = update_time;
if (preset->sync_info == "update") if (preset->sync_info == "update")
@@ -2653,6 +2659,9 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
iter->base_id = based_id; iter->base_id = based_id;
iter->filament_id = cloud_filament_id; iter->filament_id = cloud_filament_id;
update_alias(*iter); update_alias(*iter);
// Persist the cloud-assigned identity to disk, mirroring the equal/newer branch
// above; otherwise the id stays only in memory and the next launch rewrites it.
iter->save_info();
//presets_loaded.emplace_back(*it->second); //presets_loaded.emplace_back(*it->second);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%") BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id; % iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
+1 -1
View File
@@ -603,7 +603,7 @@ public:
void update_after_user_presets_loaded(); void update_after_user_presets_loaded();
//BBS: get user presets //BBS: get user presets
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets); int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time); void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id);
bool need_sync(std::string name, std::string setting_id, long long update_time); bool need_sync(std::string name, std::string setting_id, long long update_time);
//BBS: add function to generate differed preset for save //BBS: add function to generate differed preset for save
+5 -3
View File
@@ -5381,13 +5381,15 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
bool process_multi_extruder = false; bool process_multi_extruder = false;
std::vector<int> filament_variant_index; std::vector<int> filament_variant_index;
size_t extruder_variant_count; size_t extruder_variant_count;
if (!config.option<ConfigOptionInts>("filament_self_index")) { // A config loaded over the full defaults has a one-entry index even when the file has none.
std::vector<int>& filament_self_indice = config.option<ConfigOptionInts>("filament_self_index", true)->values; ConfigOptionInts* filament_self_index_opt = config.option<ConfigOptionInts>("filament_self_index", true);
if (filament_self_index_opt->size() < num_filaments) {
std::vector<int>& filament_self_indice = filament_self_index_opt->values;
filament_self_indice.resize(num_filaments); filament_self_indice.resize(num_filaments);
for (int index = 0; index < num_filaments; index++) for (int index = 0; index < num_filaments; index++)
filament_self_indice[index] = index + 1; filament_self_indice[index] = index + 1;
} }
std::vector<int> filament_self_indice = std::move(config.option<ConfigOptionInts>("filament_self_index")->values); std::vector<int> filament_self_indice = std::move(filament_self_index_opt->values);
// ORCA: Initialize filament_extruder_variant for backward compatibility with old 3mf files // ORCA: Initialize filament_extruder_variant for backward compatibility with old 3mf files
// that don't have this option saved or have it with default single-element value // that don't have this option saved or have it with default single-element value
ConfigOptionStrings* filament_extruder_variant_opt = config.option<ConfigOptionStrings>("filament_extruder_variant"); ConfigOptionStrings* filament_extruder_variant_opt = config.option<ConfigOptionStrings>("filament_extruder_variant");
+25
View File
@@ -5340,6 +5340,31 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
} }
} }
void Print::reload_gcode_moves(GCodeProcessorResult* result) const
{
GCodeProcessor processor;
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
const Vec3d origin = this->get_plate_origin();
processor.set_xy_offset(origin(0), origin(1));
// Estimate the per-move times with the same nozzle-grouping slot context as the export.
if (result->nozzle_group_result)
processor.initialize_from_context(result->nozzle_group_result);
try {
processor.process_file(result->filename);
} catch (const std::exception& ex) {
// The edited file is what gets printed, so failing to preview it must not fail the slice.
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": cannot re-read the G-code file " << result->filename << ": " << ex.what();
std::lock_guard<std::mutex> lock(result->result_mutex);
result->lines_ends.clear();
return;
}
GCodeProcessorResult& reloaded = processor.result();
std::lock_guard<std::mutex> lock(result->result_mutex);
result->moves = std::move(reloaded.moves);
result->lines_ends = std::move(reloaded.lines_ends);
}
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
{ {
if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty()) if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty())
+9
View File
@@ -12,6 +12,7 @@
#include "PrintBase.hpp" #include "PrintBase.hpp"
#include "Fill/FillAdaptive.hpp" #include "Fill/FillAdaptive.hpp"
#include "Fill/FillLightning.hpp" #include "Fill/FillLightning.hpp"
#include "Fill/FillTpmsAdaptive.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "ExtrusionEntityCollection.hpp" #include "ExtrusionEntityCollection.hpp"
@@ -406,6 +407,7 @@ public:
double max_z() const { return m_max_z; } double max_z() const { return m_max_z; }
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model. // Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
const Point& center_offset() const { return m_center_offset; } const Point& center_offset() const { return m_center_offset; }
const TpmsRadialField* tpms_radial_field(TpmsAdaptiveMode mode) const { return m_tpms_radial_fields[size_t(mode)].get(); }
// BBS // BBS
void generate_support_preview(); void generate_support_preview();
@@ -650,6 +652,7 @@ private:
FillAdaptive::RegionOctrees prepare_adaptive_infill_data( FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const; const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
FillLightning::GeneratorPtr prepare_lightning_infill_data(); FillLightning::GeneratorPtr prepare_lightning_infill_data();
TpmsRadialFields prepare_tpms_radial_fields() const;
// BBS // BBS
SupportNecessaryType is_support_necessary(); SupportNecessaryType is_support_necessary();
@@ -700,6 +703,7 @@ private:
FillAdaptive::RegionOctrees m_adaptive_fill_octrees; FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
std::vector<BoundingBox> m_separated_body_bboxes; std::vector<BoundingBox> m_separated_body_bboxes;
FillLightning::GeneratorPtr m_lightning_generator; FillLightning::GeneratorPtr m_lightning_generator;
TpmsRadialFields m_tpms_radial_fields;
std::vector < VolumeSlices > firstLayerObjSliceByVolume; std::vector < VolumeSlices > firstLayerObjSliceByVolume;
std::vector<groupedVolumeSlices> firstLayerObjSliceByGroups; std::vector<groupedVolumeSlices> firstLayerObjSliceByGroups;
@@ -1287,6 +1291,11 @@ public:
void set_gcode_file_ready(); void set_gcode_file_ready();
void set_gcode_file_invalidated(); void set_gcode_file_invalidated();
void export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); void export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
// Re-reads the moves and line offsets of `result` from its G-code file after the file was rewritten in
// place (post-processing scripts or plugins), so the preview and its G-code window follow the file on
// disk. Everything else in `result` was computed while slicing and is kept. If the file cannot be
// re-read, the moves are kept and the line offsets are cleared, which hides the G-code window.
void reload_gcode_moves(GCodeProcessorResult* result) const;
//BBS: add modify_count logic //BBS: add modify_count logic
int get_modified_count() const {return m_modified_count;} int get_modified_count() const {return m_modified_count;}
//BBS: add status for whether support used //BBS: add status for whether support used
+87
View File
@@ -364,6 +364,26 @@ static t_config_enum_values s_keys_map_SurfaceFillOrder{
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SurfaceFillOrder) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SurfaceFillOrder)
//Orca
static t_config_enum_values s_keys_map_TpmsAdaptiveMode{
{ "disabled", int(TpmsAdaptiveMode::Disabled) },
{ "distance_warp", int(TpmsAdaptiveMode::DistanceWarp) },
{ "smooth_blend", int(TpmsAdaptiveMode::SmoothBlend) },
{ "stepped_shells", int(TpmsAdaptiveMode::SteppedShells) },
{ "lobes", int(TpmsAdaptiveMode::Lobes) },
{ "normal_z", int(TpmsAdaptiveMode::NormalZ) },
{ "normal_y", int(TpmsAdaptiveMode::NormalY) },
{ "normal_x", int(TpmsAdaptiveMode::NormalX) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TpmsAdaptiveMode)
static t_config_enum_values s_keys_map_TpmsAdaptiveGradient{
{ "linear", int(TpmsAdaptiveGradient::Linear) },
{ "quadratic", int(TpmsAdaptiveGradient::Quadratic) },
{ "exponential", int(TpmsAdaptiveGradient::Exponential) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TpmsAdaptiveGradient)
//BBS //BBS
static t_config_enum_values s_keys_map_PrintSequence { static t_config_enum_values s_keys_map_PrintSequence {
{ "by layer", int(PrintSequence::ByLayer) }, { "by layer", int(PrintSequence::ByLayer) },
@@ -3620,6 +3640,73 @@ void PrintConfigDef::init_fff_params()
def->max = 10; // Maximum number of lines for infill pattern def->max = 10; // Maximum number of lines for infill pattern
def->set_default_value(new ConfigOptionInt(1)); def->set_default_value(new ConfigOptionInt(1));
def = this->add("tpms_adaptive", coEnum);
def->label = L("Adaptive density (experimental)");
def->category = L("Strength");
def->tooltip = L("Grades the Gyroid and TPMS infill inside the object: its cells grow from the surface of the "
"object towards its center. The sparse infill density is used at the surface and the interior "
"density at the center.\n"
"Distance warp, Smooth blend and Stepped shells follow the distance to the nearest surface, "
"including the top and bottom, with the interior density at the point farthest from it:\n"
" - Distance warp: one continuous pattern, stretched and sheared where the distance changes "
"across directions, as in plates and long parts.\n"
" - Smooth blend: the patterns of neighbouring densities blended into each other, with small "
"loops where they meet.\n"
" - Stepped shells: shells of the regular pattern at densities about 1.5 times apart, their "
"lines joined along the shell boundaries.\n"
" - Lobes: follows the 3D shape of the object, including its top and bottom. Every lobe, a part "
"joined to the rest by a narrower neck, is graded towards its own center.\n"
" - Normal Z, Y or X: follows the sections of the object normal to that axis, so the density "
"does not change along it.");
def->enum_keys_map = &ConfigOptionEnum<TpmsAdaptiveMode>::get_enum_values();
def->enum_values.push_back("disabled");
def->enum_values.push_back("distance_warp");
def->enum_values.push_back("smooth_blend");
def->enum_values.push_back("stepped_shells");
def->enum_values.push_back("lobes");
def->enum_values.push_back("normal_z");
def->enum_values.push_back("normal_y");
def->enum_values.push_back("normal_x");
def->enum_labels.push_back(L("Disabled"));
def->enum_labels.push_back(L("Distance warp"));
def->enum_labels.push_back(L("Smooth blend"));
def->enum_labels.push_back(L("Stepped shells"));
def->enum_labels.push_back(L("Lobes"));
def->enum_labels.push_back(L("Normal Z"));
def->enum_labels.push_back(L("Normal Y"));
def->enum_labels.push_back(L("Normal X"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<TpmsAdaptiveMode>(TpmsAdaptiveMode::Disabled));
def = this->add("tpms_interior_density", coPercent);
def->label = L("Interior density");
def->category = L("Strength");
def->tooltip = L("Density of the adaptive infill at the center of the object.");
def->sidetext = "%";
def->min = 1;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercent(5));
def = this->add("tpms_adaptive_gradient", coEnum);
def->label = L("Adaptive gradient");
def->category = L("Strength");
def->tooltip = L("How the density changes from the surface to the center of the object.\n"
"Linear: the density changes at a constant rate.\n"
"Quadratic: the density stays close to the sparse infill density near the surface and "
"changes faster towards the center.\n"
"Exponential: the density changes quickly just below the surface and levels off towards "
"the center.");
def->enum_keys_map = &ConfigOptionEnum<TpmsAdaptiveGradient>::get_enum_values();
def->enum_values.push_back("linear");
def->enum_values.push_back("quadratic");
def->enum_values.push_back("exponential");
def->enum_labels.push_back(L("Linear"));
def->enum_labels.push_back(L("Quadratic"));
def->enum_labels.push_back(L("Exponential"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<TpmsAdaptiveGradient>(TpmsAdaptiveGradient::Linear));
// Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z // Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z
// (vertical) axis at low infill density to shorten the effective column length under // (vertical) axis at low infill density to shorten the effective column length under
// Z-axis compression. Filament use at the same `sparse_infill_density` setting is // Z-axis compression. Filament use at the same `sparse_infill_density` setting is
+27
View File
@@ -172,6 +172,8 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
// Orca: Infill patterns laid out by an octree, which each connected body always gets of its own. // Orca: Infill patterns laid out by an octree, which each connected body always gets of its own.
inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; } inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; }
// Orca: Infill patterns graded by the "tpms_adaptive" option.
inline bool is_tpms_adaptive_pattern(InfillPattern pattern) { return pattern == ipGyroid || pattern == ipTpmsD || pattern == ipTpmsFK; }
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option. // Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more // Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
@@ -257,6 +259,26 @@ enum class SurfaceFillOrder {
Count, Count,
}; };
// Orca: what the adaptive TPMS density follows: the 3D shape of the object, or its 2D sections normal to an axis.
enum class TpmsAdaptiveMode {
Disabled,
DistanceWarp,
SmoothBlend,
SteppedShells,
Lobes,
NormalZ,
NormalY,
NormalX,
Count,
};
// Orca: how the adaptive TPMS density changes from the object surface to its deepest point.
enum class TpmsAdaptiveGradient {
Linear,
Quadratic,
Exponential,
};
//BBS //BBS
enum class PrintSequence { enum class PrintSequence {
ByLayer, ByLayer,
@@ -774,6 +796,8 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TpmsAdaptiveMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TpmsAdaptiveGradient)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@@ -1450,6 +1474,9 @@ PRINT_CONFIG_CLASS_DEFINE(
// Orca: // Orca:
((ConfigOptionFloatOrPercent, infill_combination_max_layer_height)) ((ConfigOptionFloatOrPercent, infill_combination_max_layer_height))
((ConfigOptionInt, fill_multiline)) ((ConfigOptionInt, fill_multiline))
((ConfigOptionEnum<TpmsAdaptiveMode>, tpms_adaptive))
((ConfigOptionPercent, tpms_interior_density))
((ConfigOptionEnum<TpmsAdaptiveGradient>, tpms_adaptive_gradient))
((ConfigOptionBool, gyroid_optimized)) ((ConfigOptionBool, gyroid_optimized))
// Ironing options // Ironing options
((ConfigOptionEnum<IroningType>, ironing_type)) ((ConfigOptionEnum<IroningType>, ironing_type))
+50 -62
View File
@@ -12,6 +12,7 @@
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "ConnectedBodies.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "Layer.hpp" #include "Layer.hpp"
@@ -31,6 +32,7 @@
#include "Fill/FillAdaptive.hpp" #include "Fill/FillAdaptive.hpp"
#include "Fill/Fill.hpp" #include "Fill/Fill.hpp"
#include "Fill/FillLightning.hpp" #include "Fill/FillLightning.hpp"
#include "Fill/FillTpmsAdaptive.hpp"
#include "format.hpp" #include "format.hpp"
#include "AABBTreeIndirect.hpp" #include "AABBTreeIndirect.hpp"
#include "AABBTreeLines.hpp" #include "AABBTreeLines.hpp"
@@ -746,69 +748,19 @@ void PrintObject::prepare_infill()
for (Layer *layer : m_layers) for (Layer *layer : m_layers)
layer->lslices_separated_component_ids.clear(); layer->lslices_separated_component_ids.clear();
if (needs_separated_components) { if (needs_separated_components) {
const size_t nl = m_layers.size(); std::vector<const ExPolygons *> islands;
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer islands.reserve(m_layers.size());
for (size_t i = 0; i < nl; ++ i) for (const Layer *layer : m_layers)
offset[i + 1] = offset[i] + m_layers[i]->lslices.size(); islands.emplace_back(&layer->lslices);
const size_t nreg = offset[nl]; size_t bodies = 0;
// Orca: Union-find over every (layer, island). std::vector<std::vector<size_t>> ids = connected_bodies(islands, bodies, [this]() { m_print->throw_if_canceled(); });
std::vector<size_t> parent(nreg); // Orca: Merge the bounding boxes of the islands of each body.
for (size_t i = 0; i < nreg; ++ i) parent[i] = i; m_separated_body_bboxes.assign(bodies, BoundingBox());
auto find = [&parent](size_t x) { for (size_t i = 0; i < m_layers.size(); ++ i) {
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
return x;
};
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
// Orca: Index the smaller of two consecutive layers instead of scanning every
// pair of islands. The tree prunes distant boxes on fragmented models; exact
// polygon intersections still decide connectivity for the remaining candidates.
for (size_t i = 0; i + 1 < nl; ++ i) {
m_print->throw_if_canceled();
size_t layer_a = i, layer_b = i + 1;
if (m_layers[layer_a]->lslices.size() < m_layers[layer_b]->lslices.size())
std::swap(layer_a, layer_b);
const Layer *la = m_layers[layer_a], *lb = m_layers[layer_b];
if (lb->lslices.empty())
continue;
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(lb->lslices.size());
for (size_t b = 0; b < lb->lslices.size(); ++ b)
bboxes.emplace_back(b, lb->lslices_bboxes[b]);
IslandTree tree;
tree.build_modify_input(bboxes);
for (size_t a = 0; a < la->lslices.size(); ++ a) {
const IslandTree::BoundingBox query(la->lslices_bboxes[a].min, la->lslices_bboxes[a].max);
AABBTreeIndirect::traverse(tree,
[&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
[&](const IslandTree::Node &node) {
const size_t b = node.idx;
// Orca: Tree boxes include an epsilon, so retain the original box
// filter. Already-connected islands cannot change the partition
// and need no further polygon intersection.
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
find(offset[layer_a] + a) != find(offset[layer_b] + b) &&
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
unite(offset[layer_a] + a, offset[layer_b] + b);
return true;
});
}
}
// Orca: Number the bodies by their first island and merge the bounding boxes of their islands.
std::vector<size_t> body_of_root(nreg, size_t(-1));
for (size_t i = 0; i < nl; ++ i) {
Layer *layer = m_layers[i]; Layer *layer = m_layers[i];
layer->lslices_separated_component_ids.resize(layer->lslices.size()); for (size_t a = 0; a < layer->lslices.size(); ++ a)
for (size_t a = 0; a < layer->lslices.size(); ++ a) { m_separated_body_bboxes[ids[i][a]].merge(layer->lslices_bboxes[a]);
size_t &body = body_of_root[find(offset[i] + a)]; layer->lslices_separated_component_ids = std::move(ids[i]);
if (body == size_t(-1)) {
body = m_separated_body_bboxes.size();
m_separated_body_bboxes.emplace_back();
}
m_separated_body_bboxes[body].merge(layer->lslices_bboxes[a]);
layer->lslices_separated_component_ids[a] = body;
}
} }
} }
@@ -1297,6 +1249,38 @@ FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this), [this]() -> void { this->throw_if_canceled(); }) : FillLightning::GeneratorPtr(); return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this), [this]() -> void { this->throw_if_canceled(); }) : FillLightning::GeneratorPtr();
} }
TpmsRadialFields PrintObject::prepare_tpms_radial_fields() const
{
TpmsRadialFields fields;
std::array<bool, size_t(TpmsAdaptiveMode::Count)> modes{};
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id)
if (const PrintRegionConfig &config = this->printing_region(region_id).config();
config.sparse_infill_density > 0 && config.sparse_infill_density < 100 && is_tpms_adaptive_pattern(config.sparse_infill_pattern))
modes[size_t(config.tpms_adaptive.value)] = true;
modes[size_t(TpmsAdaptiveMode::Disabled)] = false;
if (std::find(modes.begin(), modes.end(), true) == modes.end() || m_layers.empty())
return fields;
std::vector<TpmsRadialField::Slice> slices;
slices.reserve(m_layers.size());
BoundingBox bbox;
for (const Layer *layer : m_layers) {
slices.push_back({layer->bottom_z(), layer->print_z, &layer->lslices});
bbox.merge(get_extents(layer->lslices));
}
if (!bbox.defined)
return fields;
for (size_t mode = 0; mode < modes.size(); ++mode) {
if (!modes[mode])
continue;
// Without a field, the infill falls back to the regular pattern.
auto field = std::make_unique<TpmsRadialField>(slices, bbox, TpmsAdaptiveMode(mode), [this]() { m_print->throw_if_canceled(); });
if (!field->empty())
fields[mode] = std::move(field);
}
return fields;
}
void PrintObject::clear_layers() void PrintObject::clear_layers()
{ {
if (!m_shared_object) { if (!m_shared_object) {
@@ -1700,6 +1684,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "infill_overhang_angle") { || opt_key == "infill_overhang_angle") {
steps.emplace_back(posInfill); steps.emplace_back(posInfill);
} else if (opt_key == "sparse_infill_pattern" } else if (opt_key == "sparse_infill_pattern"
|| opt_key == "tpms_adaptive"
|| opt_key == "tpms_interior_density"
|| opt_key == "tpms_adaptive_gradient"
// Orca: Body centering now also determines bridge anchors during preparation. // Orca: Body centering now also determines bridge anchors during preparation.
// Invalidating preparation also invalidates infill, including top/bottom surfaces. // Invalidating preparation also invalidates infill, including top/bottom surfaces.
|| opt_key == "center_of_surface_pattern" || opt_key == "center_of_surface_pattern"
@@ -3207,6 +3194,7 @@ void PrintObject::bridge_over_infill()
} }
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer); this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer);
this->m_tpms_radial_fields = this->prepare_tpms_radial_fields();
std::vector<size_t> layers_to_generate_infill; std::vector<size_t> layers_to_generate_infill;
for (const auto &pair : surfaces_by_layer) { for (const auto &pair : surfaces_by_layer) {
+34
View File
@@ -1495,6 +1495,40 @@ float its_volume(const indexed_triangle_set &its)
return volume; return volume;
} }
MassProperties MassProperties::transformed(const Transform3d &trafo) const
{
const Matrix3d linear = trafo.linear();
const double scale = std::abs(linear.determinant());
return { mass * scale, volume * scale, trafo * center, linear * spread * linear.transpose() };
}
MassProperties its_mass_properties(const indexed_triangle_set &its)
{
if (its.indices.empty())
return {};
// Signed tetrahedra fanned from a mesh vertex, not the origin, to keep the sums precise far from it.
const Vec3d p0 = its.vertices.front().cast<double>();
double volume6 = 0.;
Vec3d moment24 = Vec3d::Zero();
Matrix3d second120 = Matrix3d::Zero();
for (const stl_triangle_vertex_indices &face : its.indices) {
const Vec3d a = its.vertices[face(0)].cast<double>() - p0;
const Vec3d b = its.vertices[face(1)].cast<double>() - p0;
const Vec3d c = its.vertices[face(2)].cast<double>() - p0;
const Vec3d s = a + b + c;
const double v = a.dot(b.cross(c));
volume6 += v;
moment24 += v * s;
second120 += v * (a * a.transpose() + b * b.transpose() + c * c.transpose() + s * s.transpose());
}
if (volume6 == 0.)
return {};
const Vec3d center = moment24 / (4. * volume6);
const double volume = std::abs(volume6) / 6.;
return { volume, volume, p0 + center, second120 / (20. * volume6) - center * center.transpose() };
}
float its_average_edge_length(const indexed_triangle_set &its) float its_average_edge_length(const indexed_triangle_set &its)
{ {
if (its.indices.empty()) if (its.indices.empty())
+15
View File
@@ -9,6 +9,7 @@
#include <array> #include <array>
#include <cereal/specialize.hpp> #include <cereal/specialize.hpp>
#include <functional> #include <functional>
#include <utility>
#include <vector> #include <vector>
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "Line.hpp" #include "Line.hpp"
@@ -324,6 +325,20 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
} }
float its_volume(const indexed_triangle_set &its); float its_volume(const indexed_triangle_set &its);
// Mass, volume and center of mass of a solid, and the mean over its mass of (x - center)(x - center)^T, from which its
// moments of inertia about axes through the center follow.
struct MassProperties
{
double mass{ 0. };
double volume{ 0. };
Vec3d center{ Vec3d::Zero() };
Matrix3d spread{ Matrix3d::Zero() };
// Under an affine map, which scales mass and volume by its determinant.
MassProperties transformed(const Transform3d &trafo) const;
};
// The solid a closed mesh bounds at unit density, whichever way its faces turn; nothing for a zero volume.
MassProperties its_mass_properties(const indexed_triangle_set &its);
float its_average_edge_length(const indexed_triangle_set &its); float its_average_edge_length(const indexed_triangle_set &its);
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B); void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
+95 -5
View File
@@ -149,6 +149,56 @@ std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std:
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name); return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
} }
// A plugin page capability exposed as a speed-dial action. source_key = plugin_key
// (identity), so a plugin display-name change does not re-key the action.
struct PluginPageAction : AppAction
{
static constexpr const char* kIdPrefix = "plugin_page_action";
std::string plugin_key;
std::string capability;
// The id an action for (plugin_key, capability) would have - lets refresh_page_capability
// remove a gone capability without materialising the action.
static std::string id_for(const std::string& plugin_key, const std::string& capability)
{ return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key); }
PluginPageAction(std::string plugin_key_in, std::string capability_in, std::string source_name)
: AppAction(kIdPrefix,
capability_in.empty() ? plugin_key_in : capability_in, // title
plugin_key_in, // source_key
std::move(source_name))
, plugin_key(std::move(plugin_key_in))
, capability(std::move(capability_in))
{
// Stay classified as a plugin: grouped under "Plugins" and gated by the same run-confirm.
this->kind = AppActionKind::Plugin;
// Icon is left empty on purpose: the webview builds resources/images/<icon>.svg, which a
// plugin filesystem icon path would not resolve to.
}
AppActionRunResult run(const std::string& /*param*/) const override
{
MainFrame* mf = wxGetApp().mainframe;
if (mf)
mf->plugin_pages().select_page({PluginCapabilityType::Pages, capability, plugin_key});
return {AppActionRunResult::Level::Success};
}
};
// Builds an action for a page capability, or nullptr if it is not a currently-loaded,
// enabled page capability.
std::unique_ptr<AppAction> make_page_action(const std::string& plugin_key, const std::string& capability, const std::string& source_name)
{
PluginManager& manager = PluginManager::instance();
if (!manager.is_plugin_loaded(plugin_key))
return nullptr;
// only_enabled defaults true, so a disabled capability resolves to nullptr here.
if (!manager.get_plugin_capability({PluginCapabilityType::Pages, capability, plugin_key}))
return nullptr;
return std::make_unique<PluginPageAction>(plugin_key, capability, source_name);
}
// ---- built-in command actions (the speed dial "commands" section) ------ // ---- built-in command actions (the speed dial "commands" section) ------
constexpr const char* kSettingPrefix = "orca_setting"; constexpr const char* kSettingPrefix = "orca_setting";
@@ -311,13 +361,20 @@ void ActionRegistry::init()
}); });
}; };
auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) { auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) {
if (capability.type != PluginCapabilityType::Script || !wxTheApp || wxGetApp().is_closing()) if (capability.type != PluginCapabilityType::Script && capability.type != PluginCapabilityType::Pages)
return; return;
const std::string plugin_key = capability.plugin_key; if (!wxTheApp || wxGetApp().is_closing())
const std::string name = capability.name; return;
wxGetApp().CallAfter([this, plugin_key, name, change] { const PluginCapabilityType type = capability.type;
if (!wxGetApp().is_closing()) const std::string plugin_key = capability.plugin_key;
const std::string name = capability.name;
wxGetApp().CallAfter([this, type, plugin_key, name, change] {
if (wxGetApp().is_closing())
return;
if (type == PluginCapabilityType::Script)
this->refresh_capability(plugin_key, name, change); this->refresh_capability(plugin_key, name, change);
else
this->refresh_page_capability(plugin_key, name, change);
}); });
}; };
@@ -347,6 +404,16 @@ void ActionRegistry::init()
upsert(std::move(action)); upsert(std::move(action));
} }
for (const auto& capability : manager.get_plugin_capabilities("", PluginCapabilityType::Pages)) {
if (!capability)
continue;
const std::string& key = capability->audit_plugin_key();
auto it = source_names.find(key);
const std::string& source_name = it == source_names.end() ? key : it->second;
if (auto action = make_page_action(key, capability->name(), source_name))
upsert(std::move(action));
}
// Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer). // Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer).
// Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct // Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct
// by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in // by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in
@@ -401,6 +468,12 @@ void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange
if (auto action = make_action(plugin_key, capability->name(), source_name)) if (auto action = make_action(plugin_key, capability->name(), source_name))
upsert(std::move(action)); upsert(std::move(action));
} }
for (const auto& capability : manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Pages)) {
if (!capability)
continue;
if (auto action = make_page_action(plugin_key, capability->name(), source_name))
upsert(std::move(action));
}
} }
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change) void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
@@ -420,6 +493,23 @@ void ActionRegistry::refresh_capability(const std::string& plugin_key, const std
remove(id); remove(id);
} }
void ActionRegistry::refresh_page_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
{
assert(wxThread::IsMain());
const std::string id = PluginPageAction::id_for(plugin_key, capability);
if (change == ActionChange::Removed) {
remove(id);
return;
}
PluginManager& manager = PluginManager::instance();
if (auto action = make_page_action(plugin_key, capability, find_loaded_source_name(manager, plugin_key)))
upsert(std::move(action));
else
remove(id);
}
void ActionRegistry::upsert(std::unique_ptr<AppAction> action) void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
{ {
assert(wxThread::IsMain()); assert(wxThread::IsMain());
+1
View File
@@ -239,6 +239,7 @@ private:
// one plugin's whole action set; refresh_capability touches a single capability. // one plugin's whole action set; refresh_capability touches a single capability.
void refresh_source(const std::string& plugin_key, ActionChange change); void refresh_source(const std::string& plugin_key, ActionChange change);
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change); void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
void refresh_page_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
bool m_started = false; // init() runs exactly once; guards double-subscription bool m_started = false; // init() runs exactly once; guards double-subscription
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
+5 -4
View File
@@ -283,11 +283,12 @@ void BackgroundSlicingProcess::process_fff()
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path(); m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_fff_print->export_gcode(m_temp_output_path, m_gcode_result,
[this](const ThumbnailsParams& params) { return this->render_thumbnails(params); }); [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
// Orca: BBL printers post-process the g-code in place here and never re-parse it into a fresh // Orca: BBL printers post-process the g-code in place here, in the file the G-code viewer maps, so
// GCodeProcessorResult, so m_gcode_result->nozzle_group_result (consumed by the H2C print-dispatch // the preview re-reads its moves and line offsets from the edited file. The rest of m_gcode_result,
// nozzle mapping) survives post-processing. No preservation guard is needed on this path. // including nozzle_group_result (consumed by the H2C print-dispatch nozzle mapping), is kept.
if (m_fff_print->is_BBL_printer()) { if (m_fff_print->is_BBL_printer()) {
run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config()); if (run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config()))
m_fff_print->reload_gcode_moves(m_gcode_result);
} }
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished"); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished");
+10 -2
View File
@@ -843,9 +843,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric || bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric ||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral; pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral;
// gyroid_optimized only applies when the sparse infill pattern is gyroid; // The sparse infill density is the surface density of the adaptive TPMS infill; at 100% the infill is solid.
bool have_tpms_infill = have_infill && config->option<ConfigOptionPercent>("sparse_infill_density")->value < 100 &&
is_tpms_adaptive_pattern(pattern);
toggle_line("tpms_adaptive", have_tpms_infill);
bool have_tpms_adaptive = have_tpms_infill && config->opt_enum<TpmsAdaptiveMode>("tpms_adaptive") != TpmsAdaptiveMode::Disabled;
toggle_line("tpms_interior_density", have_tpms_adaptive);
toggle_line("tpms_adaptive_gradient", have_tpms_adaptive);
// gyroid_optimized only applies when the sparse infill pattern is gyroid without adaptive density;
// hide the whole line otherwise. // hide the whole line otherwise.
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid); toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid && !have_tpms_adaptive);
// If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill. // If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill.
if (have_infill) { if (have_infill) {
+20 -2
View File
@@ -895,10 +895,16 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, f
auto update_lines = [this](uint64_t start_id, uint64_t end_id) { auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
std::vector<Line> ret; std::vector<Line> ret;
ret.reserve(end_id - start_id + 1); ret.reserve(end_id - start_id + 1);
// Orca: m_lines_ends indexes into a memory mapping, so it must be clamped to the mapping. If the
// file was modified behind our back (an in-place post-processing script that shrank it), an
// unchecked read is an access violation, which the caller's try/catch cannot catch on Windows.
const size_t file_size = m_file.size();
for (uint64_t id = start_id; id <= end_id; ++id) { for (uint64_t id = start_id; id <= end_id; ++id) {
// read line from file // read line from file
const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; // Keep one entry per id: render() indexes m_lines by (id - start_id).
const size_t original_len = m_lines_ends[id - 1] - start; const size_t start = id == 1 ? 0 : std::min(m_lines_ends[id - 2], file_size);
const size_t end = std::min(m_lines_ends[id - 1], file_size);
const size_t original_len = end > start ? end - start : 0;
// A character is four bytes at most, so 55 of them always fit in 220. // A character is four bytes at most, so 55 of them always fit in 220.
const size_t len = std::min(original_len, (size_t) 55 * 4); const size_t len = std::min(original_len, (size_t) 55 * 4);
std::string gline(m_file.data() + start, len); std::string gline(m_file.data() + start, len);
@@ -1435,6 +1441,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
wxGetApp().plater()->schedule_background_process(); wxGetApp().plater()->schedule_background_process();
return; return;
} }
m_plate_mass = gcode_result.plate_mass;
m_object_masses = gcode_result.object_masses;
m_body_masses = gcode_result.body_masses;
m_support_masses = gcode_result.support_masses;
// convert data from PrusaSlicer format to libvgcode format. // convert data from PrusaSlicer format to libvgcode format.
// Belt printers: when the designed (upright) view is active, back-transform // Belt printers: when the designed (upright) view is active, back-transform
@@ -1870,6 +1880,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data) void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
{ {
m_loaded_as_preview = true; m_loaded_as_preview = true;
m_plate_mass = {};
m_object_masses.clear();
m_body_masses.clear();
m_support_masses.clear();
m_move_type_counts.fill(0); m_move_type_counts.fill(0);
for (auto& move_type_times : m_move_type_times) for (auto& move_type_times : m_move_type_times)
@@ -1949,6 +1963,10 @@ void GCodeViewer::reset()
m_move_type_distances.fill(0.0f); m_move_type_distances.fill(0.0f);
m_print_statistics.reset(); m_print_statistics.reset();
m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>(); m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
m_plate_mass = {};
m_object_masses.clear();
m_body_masses.clear();
m_support_masses.clear();
m_left_extruder_filament.clear(); m_left_extruder_filament.clear();
m_right_extruder_filament.clear(); m_right_extruder_filament.clear();
m_sequential_view.gcode_window.reset(); m_sequential_view.gcode_window.reset();
+8
View File
@@ -260,6 +260,10 @@ private:
GCodeProcessorResult::SettingsIds m_settings_ids; GCodeProcessorResult::SettingsIds m_settings_ids;
std::vector<CustomGCode::Item> m_custom_gcode_per_print_z; std::vector<CustomGCode::Item> m_custom_gcode_per_print_z;
GCodeProcessorResult::ObjectMass m_plate_mass;
std::vector<GCodeProcessorResult::ObjectMass> m_object_masses;
std::vector<GCodeProcessorResult::ObjectMass> m_body_masses;
std::vector<GCodeProcessorResult::ObjectMass> m_support_masses;
bool m_contained_in_bed{ true }; bool m_contained_in_bed{ true };
mutable bool m_no_render_path { false }; mutable bool m_no_render_path { false };
@@ -343,6 +347,10 @@ public:
std::vector<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); } std::vector<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
const std::array<size_t,2> &get_layers_z_range() const { return m_viewer.get_layers_view_range(); } const std::array<size_t,2> &get_layers_z_range() const { return m_viewer.get_layers_view_range(); }
const GCodeProcessorResult::ObjectMass& get_plate_mass() const { return m_plate_mass; }
const std::vector<GCodeProcessorResult::ObjectMass>& get_object_masses() const { return m_object_masses; }
const std::vector<GCodeProcessorResult::ObjectMass>& get_body_masses() const { return m_body_masses; }
const std::vector<GCodeProcessorResult::ObjectMass>& get_support_masses() const { return m_support_masses; }
size_t get_vertices_count() const { return m_viewer.get_vertices_count(); } size_t get_vertices_count() const { return m_viewer.get_vertices_count(); }
size_t get_layers_count() const { return m_viewer.get_layers_count(); } size_t get_layers_count() const { return m_viewer.get_layers_count(); }
// ORCA: realistic view. Changes whenever the toolpaths casting shadows do. // ORCA: realistic view. Changes whenever the toolpaths casting shadows do.
+338
View File
@@ -85,7 +85,9 @@
#include "3DScene.hpp" #include "3DScene.hpp"
#include "BackgroundSlicingProcess.hpp" #include "BackgroundSlicingProcess.hpp"
#include "CameraUtils.hpp" #include "CameraUtils.hpp"
#include "GLModel.hpp"
#include "GLShader.hpp" #include "GLShader.hpp"
#include "libslic3r/ConnectedBodies.hpp"
#include "GUI.hpp" #include "GUI.hpp"
#include "Tab.hpp" #include "Tab.hpp"
#include "GUI_Preview.hpp" #include "GUI_Preview.hpp"
@@ -136,6 +138,7 @@
#include <tbb/spin_mutex.h> #include <tbb/spin_mutex.h>
#include <boost/functional/hash.hpp> #include <boost/functional/hash.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
@@ -1000,6 +1003,325 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
} }
} }
// The sums a solid adds to a marker.
static GCodeProcessorResult::ObjectMass::Sum mass_sum(const MassProperties& solid)
{
return { solid.mass, solid.volume, solid.mass * solid.center,
solid.mass * (solid.spread.diagonal() + solid.center.cwiseProduct(solid.center)) };
}
// On screen, of the plates, the objects, the supports and the bodies, each smaller than the one before, so that markers at
// one place still show.
static constexpr std::array<double, 4> marker_radii{ 9., 7., 6., 5. };
// As the canvas toolbar scales for the display's DPI.
static double marker_scale(const GLCanvas3D& canvas)
{
double scale = canvas.get_scale();
#ifdef WIN32
scale *= double(get_dpi_for_window(wxGetApp().GetTopWindow())) / double(DPI_DEFAULT);
#endif // WIN32
return scale;
}
GLCanvas3D::CenterOfMass::Markers GLCanvas3D::CenterOfMass::model_markers(const GLCanvas3D& canvas)
{
Markers markers;
if (canvas.get_model() == nullptr)
return markers;
struct Instance
{
Transform3d trafo;
std::vector<const GLVolume*> volumes;
};
std::map<int, std::map<int, Instance>> objects;
const ModelObjectPtrs& model_objects = canvas.get_model()->objects;
for (const GLVolume* volume : canvas.get_volumes().volumes) {
const int obj_idx = volume->object_idx();
const int vol_idx = volume->volume_idx();
if (!volume->is_active || volume->is_wipe_tower || obj_idx < 0 || obj_idx >= int(model_objects.size()) || vol_idx < 0 ||
vol_idx >= int(model_objects[obj_idx]->volumes.size()))
continue;
Instance& instance = objects[obj_idx][volume->instance_idx()];
instance.trafo = volume->get_instance_transformation().get_matrix();
instance.volumes.emplace_back(volume);
}
// From the filament presets, as the plater config holds the values of the last filament edited only.
const PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
std::vector<double> filament_densities;
for (const std::string& name : preset_bundle.filament_presets)
filament_densities.emplace_back(preset_bundle.filaments.find_preset(name, true)->config.opt_float("filament_density", 0));
const auto density = [&filament_densities](const ModelVolume& volume) {
const size_t filament = size_t(std::max(1, volume.extruder_id()));
const double density = filament <= filament_densities.size() ? filament_densities[filament - 1] : 0.;
return density > 0. ? density : double(DEFAULT_FILAMENT_DENSITY);
};
// One per plate, of the instances on it.
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
std::map<int, Marker> plates;
std::map<size_t, MassProperties> meshes;
std::map<size_t, Bodies> bodies;
for (const auto& [obj_idx, instances] : objects) {
const ModelObject& object = *model_objects[obj_idx];
// An assembly is sliced, so that its overlapping parts are united and its negative volumes cut away, in the
// order of its volumes, as the later one prints where two overlap.
std::vector<const GLVolume*> volumes = instances.begin()->second.volumes;
std::sort(volumes.begin(), volumes.end(), [](const GLVolume* l, const GLVolume* r) { return l->volume_idx() < r->volume_idx(); });
std::vector<MeshInPlace> solids;
std::vector<double> densities;
std::vector<MeshInPlace> negatives;
std::vector<Bodies::Volume> sliced;
for (const GLVolume* volume : volumes) {
const ModelVolume& model_volume = *object.volumes[volume->volume_idx()];
if (!model_volume.is_model_part() && !model_volume.is_negative_volume())
continue;
const Transform3d trafo = volume->get_volume_transformation().get_matrix();
if (model_volume.is_model_part()) {
solids.emplace_back(&model_volume.mesh().its, trafo);
densities.emplace_back(density(model_volume));
} else
negatives.emplace_back(&model_volume.mesh().its, trafo);
sliced.push_back({ model_volume.id().id, model_volume.is_negative_volume(), model_volume.is_model_part() ? densities.back() : 0., trafo });
}
const std::vector<SolidBody>* assembly = nullptr;
if (solids.size() > 1 || (!solids.empty() && !negatives.empty())) {
// Coarser while a part is dragged.
const size_t slabs = canvas.is_dragging() ? 100 : 500;
const auto cached = m_bodies.find(object.id().id);
const bool valid = cached != m_bodies.end() && cached->second.slabs >= slabs && cached->second.volumes == sliced;
Bodies& entry = bodies[object.id().id];
entry = valid ? std::move(cached->second) : Bodies{ std::move(sliced), slabs, solid_bodies(solids, densities, negatives, slabs) };
assembly = &entry.bodies;
}
for (const auto& [inst_idx, instance] : instances) {
// The box of its parts, which the object's size shows.
Marker object_marker;
object_marker.assembly = assembly != nullptr;
for (const GLVolume* volume : instance.volumes)
if (object.volumes[volume->volume_idx()]->is_model_part())
object_marker.box.merge(volume->transformed_convex_hull_bounding_box());
if (assembly != nullptr) {
std::vector<Marker> parts;
for (const SolidBody& body : *assembly)
if (body.mass > 0.) {
parts.push_back({ mass_sum(body.transformed(instance.trafo)), body.bounding_box(instance.trafo) });
object_marker.sum.add(parts.back().sum);
}
if (parts.size() > 1)
append(markers[mkBody], std::move(parts));
} else
for (const GLVolume* volume : instance.volumes) {
// The parts the object info's volume sums.
const ModelVolume& model_volume = *object.volumes[volume->volume_idx()];
if (!model_volume.is_model_part())
continue;
const auto [it, inserted] = meshes.try_emplace(model_volume.id().id);
if (inserted) {
const auto cached = m_meshes.find(it->first);
it->second = cached != m_meshes.end() ? cached->second : its_mass_properties(model_volume.mesh().its);
}
MassProperties part = it->second.transformed(volume->world_matrix());
part.mass *= density(model_volume);
object_marker.sum.add(mass_sum(part));
}
if (object_marker.sum.mass > 0.) {
if (const int plate = plate_list.find_instance(obj_idx, inst_idx); plate >= 0) {
plates[plate].sum.add(object_marker.sum);
plates[plate].box.merge(object_marker.box);
}
markers[mkObject].emplace_back(std::move(object_marker));
}
}
}
m_meshes = std::move(meshes);
m_bodies = std::move(bodies);
for (auto& [plate, marker] : plates)
markers[mkPlate].emplace_back(std::move(marker));
return markers;
}
void GLCanvas3D::CenterOfMass::render(GLCanvas3D& canvas)
{
m_drawn = {};
const bool preview = canvas.m_canvas_type == ECanvasType::CanvasPreview;
// The other gizmos work on the surface the marker would cover.
const GLGizmosManager::EType gizmo = canvas.get_gizmos_manager().get_current_type();
if (!wxGetApp().show_center_of_mass() || canvas.m_design_canvas ||
!(canvas.m_canvas_type == ECanvasType::CanvasView3D || (preview && canvas.m_render_preview)) ||
(gizmo != GLGizmosManager::Undefined && gizmo != GLGizmosManager::Move && gizmo != GLGizmosManager::Rotate &&
gizmo != GLGizmosManager::Scale && gizmo != GLGizmosManager::Flatten))
return;
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
// Preview adds markers for what is printed up to the top layer shown.
if (preview) {
const GCodeViewer& gcode_viewer = canvas.get_gcode_viewer();
m_top_layer = gcode_viewer.get_layers_z_range()[1];
const auto add = [this](const GCodeProcessorResult::ObjectMass& mass, MarkerKind kind) {
if (const Sum total = mass.total(); total.mass > 0.)
m_drawn[0][kind].push_back({ total, mass.box, mass.assembly });
if (!mass.printed_up_to_layer.empty())
if (const Sum& sum = mass.printed_up_to_layer[std::min(m_top_layer, mass.printed_up_to_layer.size() - 1)]; sum.mass > 0.)
m_drawn[1][kind].push_back({ sum, mass.box, mass.assembly });
};
add(gcode_viewer.get_plate_mass(), mkPlate);
for (const GCodeProcessorResult::ObjectMass& object : gcode_viewer.get_object_masses())
add(object, mkObject);
for (const GCodeProcessorResult::ObjectMass& body : gcode_viewer.get_body_masses())
add(body, mkBody);
for (const GCodeProcessorResult::ObjectMass& support : gcode_viewer.get_support_masses())
add(support, mkSupport);
} else
m_drawn[0] = model_markers(canvas);
if (std::all_of(m_drawn.begin(), m_drawn.end(),
[](const Markers& markers) { return std::all_of(markers.begin(), markers.end(), [](const auto& kind) { return kind.empty(); }); }))
return;
if (!m_octants[0].is_initialized()) {
// A resolution divisible by 4 puts every triangle within one octant.
const GLModel::Geometry sphere = smooth_sphere(32, 1.f);
std::array<GLModel::Geometry, 2> octants;
for (size_t i = 0; i + 2 < sphere.indices_count(); i += 3) {
const std::array<unsigned int, 3> ids = { sphere.extract_index(i), sphere.extract_index(i + 1), sphere.extract_index(i + 2) };
const Vec3f c = sphere.extract_position_3(ids[0]) + sphere.extract_position_3(ids[1]) + sphere.extract_position_3(ids[2]);
GLModel::Geometry& octant = octants[c.x() * c.y() * c.z() > 0.f ? 0 : 1];
for (const unsigned int id : ids)
octant.add_vertex(sphere.extract_position_3(id), sphere.extract_normal_3(id));
const auto n = (unsigned int)octant.vertices_count();
octant.add_triangle(n - 3, n - 2, n - 1);
}
for (size_t i = 0; i < octants.size(); ++i)
m_octants[i].init_from(std::move(octants[i]));
}
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
const double scale = marker_scale(canvas) * camera.get_inv_zoom();
// Seen through the object it lies in; culling keeps the sphere's far half behind its near one.
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_CULL_FACE));
shader->start_using();
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("view_normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3));
shader->set_uniform("emission_factor", 0.1f);
const std::array<std::array<ColorRGBA, 2>, mkCount> colors = { {
{ ColorRGBA(0.1f, 0.1f, 0.1f, 1.f), ColorRGBA::WHITE() },
{ ColorRGBA(0x5A / 255.f, 0x9B / 255.f, 0xD4 / 255.f, 1.f), ColorRGBA::WHITE() },
{ ColorRGBA(0.f, 0.6f, 0.f, 1.f), ColorRGBA(0.1f, 0.1f, 0.1f, 1.f) },
{ ColorRGBA(0.7f, 0.f, 0.f, 1.f), ColorRGBA::YELLOW() },
} };
const auto draw = [&](const Markers& markers, float alpha) {
for (size_t kind = 0; kind < mkCount; ++kind)
for (const Marker& marker : markers[kind]) {
shader->set_uniform("view_model_matrix", view_matrix * Geometry::translation_transform(marker.center()) *
Geometry::scale_transform(marker_radii[kind] * scale));
for (size_t i = 0; i < m_octants.size(); ++i) {
ColorRGBA color = colors[kind][i];
color.a(alpha);
m_octants[i].set_color(color);
m_octants[i].render();
}
}
};
// Preview fades the finished parts' markers under those of what is printed so far.
draw(m_drawn[0], preview ? 0.4f : 1.f);
draw(m_drawn[1], 1.f);
shader->stop_using();
glsafe(::glEnable(GL_DEPTH_TEST));
}
bool GLCanvas3D::CenterOfMass::on_left_down(GLCanvas3D& canvas, const Vec2d& mouse)
{
const bool shown = m_picked.has_value();
const Camera& camera = wxGetApp().plater()->get_camera();
const double scale = marker_scale(canvas) * camera.get_inv_zoom();
m_picked.reset();
// In the order they cover each other: what is printed so far over the finished print, smaller kinds over larger ones.
for (size_t set = m_drawn.size(); set-- > 0 && !m_picked;)
for (size_t kind = mkCount; kind-- > 0 && !m_picked;)
for (size_t index = 0; index < m_drawn[set][kind].size(); ++index) {
const Vec3d center = m_drawn[set][kind][index].center();
const std::vector<Vec3d> ends = { center, center + marker_radii[kind] * scale * camera.get_dir_right() };
const Points screen = CameraUtils::project(camera, ends);
if ((screen[0].cast<double>() - mouse).norm() <= (screen[1] - screen[0]).cast<double>().norm()) {
m_picked = Pick{ set, kind, index, m_drawn[set][kind].size() };
break;
}
}
if (shown || m_picked)
canvas._set_overlay_as_dirty();
return m_picked.has_value();
}
void GLCanvas3D::CenterOfMass::render_details(GLCanvas3D& canvas)
{
if (!m_picked)
return;
const Pick& pick = *m_picked;
const std::vector<Marker>& markers = m_drawn[pick.set][pick.kind];
// Gone with the markers, or with what it stood for.
if (markers.size() != pick.count) {
m_picked.reset();
return;
}
const Marker& marker = markers[pick.index];
const Sum& sum = marker.sum;
const Vec3d center = marker.center();
// About the axes through the center, from how far the mass spreads along the two others.
const Vec3d spread = (sum.second / sum.mass - center.cwiseProduct(center)).cwiseMax(0.);
const Vec3d inertia = sum.mass * Vec3d(spread.y() + spread.z(), spread.x() + spread.z(), spread.x() + spread.y());
// Beside the marker.
const Point screen = CameraUtils::project(wxGetApp().plater()->get_camera(), center);
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.set_next_window_pos(float(screen.x() + 2. * marker_radii[pick.kind] * marker_scale(canvas)), float(screen.y()), ImGuiCond_Always, 0.f, 0.5f);
const std::string title = pick.kind == mkPlate ? _u8L("Plate center of mass") :
pick.kind == mkBody ? _u8L("Part center of mass") :
pick.kind == mkSupport ? _u8L("Support center of mass") :
marker.assembly ? _u8L("Assembly center of mass") :
_u8L("Object center of mass");
bool open = true;
imgui.begin(title + "###center_of_mass", &open,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
if (ImGui::IsWindowAppearing())
imgui.set_requires_extra_frame();
if (canvas.get_canvas_type() == ECanvasType::CanvasPreview)
imgui.text(pick.set == 0 ? _u8L("Finished print") : (boost::format(_u8L("Printed up to layer %1%")) % (m_top_layer + 1)).str());
// Masses are in mg, volumes in mm³.
const auto xyz = [](const Vec3d& v, const char* format, const std::string& unit) {
return (boost::format(format) % v.x() % v.y() % v.z()).str() + " " + unit;
};
if (ImGui::BeginTable("##center_of_mass_details", 2)) {
const auto row = [](const std::string& label, const std::string& value) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, label);
ImGui::TableSetColumnIndex(1);
ImGuiWrapper::text(value);
};
row(_u8L("Weight"), (boost::format("%.2f g") % (sum.mass / 1000.)).str());
row(_u8L("Volume"), (boost::format(u8"%.2f cm³") % (sum.volume / 1000.)).str());
if (marker.box.defined) {
row(_u8L("Center in bounding box"), xyz(center - marker.box.min, "X: %.2f, Y: %.2f, Z: %.2f", _u8L("mm")));
row(_u8L("Bounding box size"), xyz(marker.box.size(), "X: %.2f, Y: %.2f, Z: %.2f", _u8L("mm")));
}
row(_u8L("Moment of inertia"), xyz(inertia / 1000., "X: %.0f, Y: %.0f, Z: %.0f", u8"g·mm²"));
ImGui::EndTable();
}
imgui.end();
if (!open) {
m_picked.reset();
canvas._set_overlay_as_dirty();
}
}
void GLCanvas3D::Tooltip::set_text(const std::string& text) void GLCanvas3D::Tooltip::set_text(const std::string& text)
{ {
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed. // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
@@ -2520,6 +2842,9 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
m_frame_profiler.mark("ssao"); m_frame_profiler.mark("ssao");
} }
// After the occlusion pass, which would shade it as the surface behind it.
m_center_of_mass.render(*this);
if (_is_fxaa_enabled()) { if (_is_fxaa_enabled()) {
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height())); _render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
m_frame_profiler.mark("fxaa"); m_frame_profiler.mark("fxaa");
@@ -4513,6 +4838,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
return; return;
} }
// A click on a center of mass marker shows its details instead of selecting.
if (evt.LeftDown() && !mouse_in_layer_editing && m_center_of_mass.on_left_down(*this, pos.cast<double>())) {
m_mouse.ignore_left_up = true;
return;
}
bool any_gizmo_active = m_gizmos.get_current() != nullptr; bool any_gizmo_active = m_gizmos.get_current() != nullptr;
std::map<MouseButton, MouseAction> button_mappings; std::map<MouseButton, MouseAction> button_mappings;
@@ -9363,6 +9694,7 @@ void GLCanvas3D::_render_overlays()
}*/ }*/
} }
m_labels.render(sorted_instances); m_labels.render(sorted_instances);
m_center_of_mass.render_details(*this);
_render_3d_navigator(); _render_3d_navigator();
@@ -10350,6 +10682,12 @@ void GLCanvas3D::_render_canvas_toolbar()
[p]{p->show_view3D_labels(!p->are_view3D_labels_shown());} [p]{p->show_view3D_labels(!p->are_view3D_labels_shown());}
); );
create_menu_item( _utf8(L("Center of mass")),
m_canvas_type != ECanvasType::CanvasAssembleView && !m_design_canvas, // work on prepare and preview
wxGetApp().show_center_of_mass(),
[this]{wxGetApp().toggle_show_center_of_mass(); m_dirty = true;}
);
// Belt printers, G-code preview only: show the raw machine-frame G-code instead of // Belt printers, G-code preview only: show the raw machine-frame G-code instead of
// the designed (upright) view. This menu is the only place the toggle lives (plus // the designed (upright) view. This menu is the only place the toggle lives (plus
// its shortcut); the reload is deferred (CallAfter) so the preview is not rebuilt // its shortcut); the reload is deferred (CallAfter) so the preview is not rebuilt
+68
View File
@@ -2,6 +2,9 @@
#define slic3r_GLCanvas3D_hpp_ #define slic3r_GLCanvas3D_hpp_
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/ConnectedBodies.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/3DScene.hpp"
#include <cstdlib> #include <cstdlib>
#include <imgui.h> #include <imgui.h>
@@ -30,6 +33,7 @@
#include "Gizmos/GLGizmosManager.hpp" #include "Gizmos/GLGizmosManager.hpp"
#include "GUI_ObjectLayers.hpp" #include "GUI_ObjectLayers.hpp"
#include "GLSelectionRectangle.hpp" #include "GLSelectionRectangle.hpp"
#include "GLModel.hpp"
#include "MeshUtils.hpp" #include "MeshUtils.hpp"
#include "GCodeViewer.hpp" #include "GCodeViewer.hpp"
#include "Camera.hpp" #include "Camera.hpp"
@@ -477,6 +481,69 @@ class GLCanvas3D
void render(const std::vector<const ModelInstance*>& sorted_instances) const; void render(const std::vector<const ModelInstance*>& sorted_instances) const;
}; };
class CenterOfMass
{
using Sum = GCodeProcessorResult::ObjectMass::Sum;
enum MarkerKind : size_t { mkPlate, mkObject, mkSupport, mkBody, mkCount };
// A marker's mass and the box of what it stands for.
struct Marker
{
Sum sum;
BoundingBoxf3 box;
// Of an object, whether it is an assembly.
bool assembly{ false };
Vec3d center() const { return sum.moment / sum.mass; }
};
// The plates', each object instance's, its supports' and each body of an assembly's.
using Markers = std::array<std::vector<Marker>, mkCount>;
// The marker's two colors of alternating octants.
std::array<GLModel, 2> m_octants;
// Mass properties at unit density of each ModelVolume's mesh, by ModelVolume id, which a new mesh changes.
std::map<size_t, MassProperties> m_meshes;
// The connected bodies of each assembly in its own coordinates, by ModelObject id, with the volumes they were sliced from.
struct Bodies
{
struct Volume
{
size_t id;
bool negative;
double density;
Transform3d trafo;
bool operator==(const Volume& other) const
{
return id == other.id && negative == other.negative && density == other.density && trafo.matrix() == other.trafo.matrix();
}
};
std::vector<Volume> volumes;
size_t slabs{ 0 };
std::vector<SolidBody> bodies;
};
std::map<size_t, Bodies> m_bodies;
// The markers drawn last: of the finished print and, in Preview, of what is printed up to the top layer shown.
std::array<Markers, 2> m_drawn;
size_t m_top_layer{ 0 };
// The marker whose details are shown, with the number of its kind then.
struct Pick
{
size_t set;
size_t kind;
size_t index;
size_t count;
};
std::optional<Pick> m_picked;
Markers model_markers(const GLCanvas3D& canvas);
public:
void render(GLCanvas3D& canvas);
// Shows the details of the marker under the mouse, else hides them; whether it hit one.
bool on_left_down(GLCanvas3D& canvas, const Vec2d& mouse);
void render_details(GLCanvas3D& canvas);
};
class Tooltip class Tooltip
{ {
std::string m_text; std::string m_text;
@@ -733,6 +800,7 @@ private:
int m_selected_extruder; int m_selected_extruder;
Labels m_labels; Labels m_labels;
CenterOfMass m_center_of_mass;
Tooltip m_tooltip; Tooltip m_tooltip;
bool m_tooltip_enabled{ true }; bool m_tooltip_enabled{ true };
Slope m_slope; Slope m_slope;
+4 -4
View File
@@ -7204,11 +7204,11 @@ void GUI_App::sync_preset(Preset* preset, bool force)
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name; BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name;
if (preset->type == Preset::Type::TYPE_FILAMENT) { if (preset->type == Preset::Type::TYPE_FILAMENT) {
preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time); preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
} else if (preset->type == Preset::Type::TYPE_PRINT) { } else if (preset->type == Preset::Type::TYPE_PRINT) {
preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time); preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
} else if (preset->type == Preset::Type::TYPE_PRINTER) { } else if (preset->type == Preset::Type::TYPE_PRINTER) {
preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time); preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
} }
} }
} }
@@ -7907,7 +7907,7 @@ void GUI_App::force_push_conflicting_preset(const std::string& setting_id)
? OrcaCloudServiceAgent::generate_uuid_for_setting_id(preset.name, user_id) ? OrcaCloudServiceAgent::generate_uuid_for_setting_id(preset.name, user_id)
: preset.setting_id; : preset.setting_id;
if (preset_id == setting_id) { if (preset_id == setting_id) {
coll->set_sync_info_and_save(preset.name, setting_id, "update", 0); coll->set_sync_info_and_save(preset.name, setting_id, "update", 0, user_id);
break; break;
} }
} }
+3
View File
@@ -435,6 +435,9 @@ public:
bool show_outline() const { return app_config->get_bool("show_outline"); } bool show_outline() const { return app_config->get_bool("show_outline"); }
void toggle_show_outline() const { app_config->set_bool("show_outline", !show_outline()); } void toggle_show_outline() const { app_config->set_bool("show_outline", !show_outline()); }
bool show_center_of_mass() const { return app_config->get_bool("show_center_of_mass"); }
void toggle_show_center_of_mass() const { app_config->set_bool("show_center_of_mass", !show_center_of_mass()); }
wxString get_inf_dialog_contect () {return m_info_dialog_content;}; wxString get_inf_dialog_contect () {return m_info_dialog_content;};
std::vector<std::string> split_str(std::string src, std::string separator); std::vector<std::string> split_str(std::string src, std::string separator);
+3
View File
@@ -148,6 +148,9 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
{"sparse_infill_density", "", 1}, {"sparse_infill_density", "", 1},
{"fill_multiline", "", 1}, {"fill_multiline", "", 1},
{"sparse_infill_pattern", "", 1}, {"sparse_infill_pattern", "", 1},
{"tpms_adaptive", "", 1},
{"tpms_interior_density", "", 1},
{"tpms_adaptive_gradient", "", 1},
{"sparse_infill_smooth_factor", "", 1}, {"sparse_infill_smooth_factor", "", 1},
{"lateral_lattice_angle_1", "", 1}, {"lateral_lattice_angle_1", "", 1},
{"lateral_lattice_angle_2", "", 1}, {"lateral_lattice_angle_2", "", 1},
+5 -1
View File
@@ -15076,7 +15076,8 @@ bool Plater::priv::undo_redo_blocked_by_job()
return false; return false;
notification_manager->push_notification(NotificationType::CustomNotification, notification_manager->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel, NotificationManager::NotificationLevel::RegularNotificationLevel,
_u8L("Cannot undo or redo while an operation is running. Stop it first.")); _u8L("Cannot undo or redo while an operation is running. Stop the operation, or wait "
"for it to finish and then retry."));
return true; return true;
} }
@@ -21229,6 +21230,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") { opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") {
update_scheduled = true; update_scheduled = true;
} }
// Orca: the center of mass markers weigh the parts by it.
else if (opt_key == "filament_density" && wxGetApp().show_center_of_mass())
p->view3D->get_canvas3d()->set_as_dirty();
} }
if (bed_shape_changed) if (bed_shape_changed)
+3
View File
@@ -2961,6 +2961,9 @@ void TabPrint::build()
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density"); optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline"); optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern"); optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("tpms_adaptive", "strength_settings_patterns#adaptive-density");
optgroup->append_single_option_line("tpms_interior_density", "strength_settings_patterns#interior-density");
optgroup->append_single_option_line("tpms_adaptive_gradient", "strength_settings_patterns#adaptive-gradient");
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized"); optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor"); optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction"); optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
+15
View File
@@ -13,6 +13,10 @@
#include <wx/gdicmn.h> #include <wx/gdicmn.h>
#include <wx/webview.h> #include <wx/webview.h>
#ifdef __WXGTK__
#include <gtk/gtk.h>
#endif
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
WebDialog::WebDialog(wxWindow* parent, WebDialog::WebDialog(wxWindow* parent,
@@ -52,6 +56,17 @@ WebDialog::WebDialog(wxWindow* parent,
Bind(wxEVT_CLOSE_WINDOW, &WebDialog::on_close_window, this); Bind(wxEVT_CLOSE_WINDOW, &WebDialog::on_close_window, this);
} }
void WebDialog::use_normal_window_type()
{
#ifdef __WXGTK__
// wxGTK types every wxDialog as a dialog, and Mutter offers no maximize for anything but a
// normal window. Mutter also stops hiding a normal window from the taskbar, so keep it hidden
// as a dialog with a parent is everywhere else.
gtk_window_set_type_hint(GTK_WINDOW(m_widget), GDK_WINDOW_TYPE_HINT_NORMAL);
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(m_widget), TRUE);
#endif
}
void WebDialog::add_user_scripts() void WebDialog::add_user_scripts()
{ {
if (wxWebView* wv = browser()) { if (wxWebView* wv = browser()) {
+4
View File
@@ -62,6 +62,10 @@ public:
bool is_open() const { return m_open; } bool is_open() const { return m_open; }
// Lets a modeless window be maximized on X11; call before Show(). Modal ones stay dialogs so the
// window manager keeps routing focus from the parent to them.
void use_normal_window_type();
// The payload submitted via window.orca.submit() (modal use), if any. // The payload submitted via window.orca.submit() (modal use), if any.
const std::optional<nlohmann::json>& result() const { return m_result; } const std::optional<nlohmann::json>& result() const { return m_result; }

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