Compare commits

..
Author SHA1 Message Date
Hanif Koh 3071d525ee Expose App, GL, Printer and Project Facts to Plugins
Plugins that help users file bug reports could only guess these from log lines: the app config is
deny-listed, and the project path, a project export and the device list had no API.

orca.host gains app_info(), gl_info() and selected_printer(), and Plater gains project_path() and
export_3mf_copy(path). The copy export leaves the project's file name, saved state and model
unchanged, does not write the signed-in account as the designer, and raises the audit event of
open(path, "w") so the plugin gets the same permissions as for writing the file itself.
2026-10-10 03:35:39 +08:00
64 changed files with 67752 additions and 1808 deletions
+6 -3
View File
@@ -707,9 +707,12 @@ jobs:
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- 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' }}
run: |
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
git push -f origin refs/tags/nightly-builds
uses: rickstaa/action-create-tag@v1
with:
tag: "nightly-builds"
tag_exists_error: false
force_push_tag: true
message: "nightly-builds"
- 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' }}
+2 -2
View File
@@ -156,7 +156,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
- uses: actions/setup-python@v6
with:
@@ -224,7 +224,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
xvfb xdotool imagemagick openbox mesa-utils \
libopengl0 libgl1 libegl1 libwebkit2gtk-4.1-0
libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0
- name: Run the parity harness
run: |
+359
View File
@@ -0,0 +1,359 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
# PrusaSlicer specifics:
# This file is backported from CMake 3.15 distribution to behave uniformly
# across all versions of CMake. It explicitly adds GLEW_STATIC compile
# definition to static targets which is needed to prevent link errors.
#[=======================================================================[.rst:
FindGLEW
--------
Find the OpenGL Extension Wrangler Library (GLEW)
Input Variables
^^^^^^^^^^^^^^^
The following variables may be set to influence this module’s behavior:
``GLEW_USE_STATIC_LIBS``
to find and create :prop_tgt:`IMPORTED` target for static linkage.
``GLEW_VERBOSE``
to output a detailed log of this module.
Imported Targets
^^^^^^^^^^^^^^^^
This module defines the following :ref:`Imported Targets <Imported Targets>`:
``GLEW::glew``
The GLEW shared library.
``GLEW::glew_s``
The GLEW static library, if ``GLEW_USE_STATIC_LIBS`` is set to ``TRUE``.
``GLEW::GLEW``
Duplicates either ``GLEW::glew`` or ``GLEW::glew_s`` based on availability.
Result Variables
^^^^^^^^^^^^^^^^
This module defines the following variables:
``GLEW_INCLUDE_DIRS``
include directories for GLEW
``GLEW_LIBRARIES``
libraries to link against GLEW
``GLEW_SHARED_LIBRARIES``
libraries to link against shared GLEW
``GLEW_STATIC_LIBRARIES``
libraries to link against static GLEW
``GLEW_FOUND``
true if GLEW has been found and can be used
``GLEW_VERSION``
GLEW version
``GLEW_VERSION_MAJOR``
GLEW major version
``GLEW_VERSION_MINOR``
GLEW minor version
``GLEW_VERSION_MICRO``
GLEW micro version
#]=======================================================================]
include(FindPackageHandleStandardArgs)
if(APPLE)
find_package(OpenGL QUIET)
if(OpenGL_FOUND)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Found OpenGL Framework.")
message(STATUS "FindGLEW: OPENGL_LIBRARIES: ${OPENGL_LIBRARIES}")
endif()
else()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: could not find GLEW library.")
endif()
return()
endif()
endif()
function(__glew_set_find_library_suffix shared_or_static)
if((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so")
elseif((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
elseif(APPLE AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib;.so")
elseif(APPLE AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
elseif(WIN32 AND "${shared_or_static}" MATCHES "SHARED")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib")
elseif(WIN32 AND "${shared_or_static}" MATCHES "STATIC")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib;.a;.dll.a")
endif()
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}" PARENT_SCOPE)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: CMAKE_FIND_LIBRARY_SUFFIXES for ${shared_or_static}: ${CMAKE_FIND_LIBRARY_SUFFIXES}")
endif()
endfunction()
if(GLEW_VERBOSE)
if(DEFINED GLEW_USE_STATIC_LIBS)
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS: ${GLEW_USE_STATIC_LIBS}.")
else()
message(STATUS "FindGLEW: GLEW_USE_STATIC_LIBS is undefined. Treated as FALSE.")
endif()
endif()
find_path(GLEW_INCLUDE_DIR GL/glew.h)
mark_as_advanced(GLEW_INCLUDE_DIR)
set(GLEW_INCLUDE_DIRS ${GLEW_INCLUDE_DIR})
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_INCLUDE_DIR: ${GLEW_INCLUDE_DIR}")
message(STATUS "FindGLEW: GLEW_INCLUDE_DIRS: ${GLEW_INCLUDE_DIRS}")
endif()
if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "x64" OR "${CMAKE_GENERATOR}" MATCHES "Win64")
set(_arch "x64")
elseif("${CMAKE_GENERATOR_PLATFORM}" MATCHES "ARM64")
set(_arch "x64") # GLEW ships one header set; ARM64 uses the x64 import path
else()
set(_arch "Win32")
endif()
set(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
__glew_set_find_library_suffix(SHARED)
find_library(GLEW_SHARED_LIBRARY_RELEASE
NAMES GLEW glew glew32
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
PATHS ENV GLEW_ROOT)
find_library(GLEW_SHARED_LIBRARY_DEBUG
NAMES GLEWd glewd glew32d
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
__glew_set_find_library_suffix(STATIC)
find_library(GLEW_STATIC_LIBRARY_RELEASE
NAMES GLEW glew glew32s
PATH_SUFFIXES lib lib64 libx32 lib/Release/${_arch}
PATHS ENV GLEW_ROOT)
find_library(GLEW_STATIC_LIBRARY_DEBUG
NAMES GLEWds GLEWd glewd glewds glew32ds
PATH_SUFFIXES lib lib64
PATHS ENV GLEW_ROOT)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES})
unset(__GLEW_CURRENT_FIND_LIBRARY_SUFFIXES)
include(SelectLibraryConfigurations)
select_library_configurations(GLEW_SHARED)
select_library_configurations(GLEW_STATIC)
if(NOT GLEW_USE_STATIC_LIBS)
set(GLEW_LIBRARIES ${GLEW_SHARED_LIBRARY})
else()
set(GLEW_LIBRARIES ${GLEW_STATIC_LIBRARY})
endif()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_RELEASE: ${GLEW_SHARED_LIBRARY_RELEASE}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_RELEASE: ${GLEW_STATIC_LIBRARY_RELEASE}")
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY_DEBUG: ${GLEW_SHARED_LIBRARY_DEBUG}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY_DEBUG: ${GLEW_STATIC_LIBRARY_DEBUG}")
message(STATUS "FindGLEW: GLEW_SHARED_LIBRARY: ${GLEW_SHARED_LIBRARY}")
message(STATUS "FindGLEW: GLEW_STATIC_LIBRARY: ${GLEW_STATIC_LIBRARY}")
message(STATUS "FindGLEW: GLEW_LIBRARIES: ${GLEW_LIBRARIES}")
endif()
# Read version from GL/glew.h file
if(EXISTS "${GLEW_INCLUDE_DIR}/GL/glew.h")
file(STRINGS "${GLEW_INCLUDE_DIR}/GL/glew.h" _contents REGEX "^VERSION_.+ [0-9]+")
if(_contents)
string(REGEX REPLACE ".*VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MAJOR "${_contents}")
string(REGEX REPLACE ".*VERSION_MINOR[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MINOR "${_contents}")
string(REGEX REPLACE ".*VERSION_MICRO[ \t]+([0-9]+).*" "\\1" GLEW_VERSION_MICRO "${_contents}")
set(GLEW_VERSION "${GLEW_VERSION_MAJOR}.${GLEW_VERSION_MINOR}.${GLEW_VERSION_MICRO}")
endif()
endif()
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: GLEW_VERSION_MAJOR: ${GLEW_VERSION_MAJOR}")
message(STATUS "FindGLEW: GLEW_VERSION_MINOR: ${GLEW_VERSION_MINOR}")
message(STATUS "FindGLEW: GLEW_VERSION_MICRO: ${GLEW_VERSION_MICRO}")
message(STATUS "FindGLEW: GLEW_VERSION: ${GLEW_VERSION}")
endif()
find_package_handle_standard_args(GLEW
REQUIRED_VARS GLEW_INCLUDE_DIRS GLEW_LIBRARIES
VERSION_VAR GLEW_VERSION)
if(NOT GLEW_FOUND)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: could not find GLEW library.")
endif()
return()
endif()
if(NOT TARGET GLEW::glew AND NOT GLEW_USE_STATIC_LIBS)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::glew imported target.")
endif()
add_library(GLEW::glew UNKNOWN IMPORTED)
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(GLEW_SHARED_LIBRARY_RELEASE)
set_property(TARGET GLEW::glew
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::glew
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
endif()
if(GLEW_SHARED_LIBRARY_DEBUG)
set_property(TARGET GLEW::glew
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::glew
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
endif()
elseif(NOT TARGET GLEW::glew_s AND GLEW_USE_STATIC_LIBS)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::glew_s imported target.")
endif()
add_library(GLEW::glew_s UNKNOWN IMPORTED)
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
set_target_properties(GLEW::glew_s PROPERTIES INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::glew_s
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(GLEW_STATIC_LIBRARY_RELEASE)
set_property(TARGET GLEW::glew_s
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::glew_s
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}")
endif()
if(GLEW_STATIC_LIBRARY_DEBUG)
set_property(TARGET GLEW::glew_s
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::glew_s
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}")
endif()
endif()
if(NOT TARGET GLEW::GLEW)
if(GLEW_VERBOSE)
message(STATUS "FindGLEW: Creating GLEW::GLEW imported target.")
endif()
add_library(GLEW::GLEW UNKNOWN IMPORTED)
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
if(APPLE)
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0")
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL")
else()
set_target_properties(GLEW::GLEW
PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
endif()
endif()
if(TARGET GLEW::glew)
if(GLEW_SHARED_LIBRARY_RELEASE)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_SHARED_LIBRARY_RELEASE}")
endif()
if(GLEW_SHARED_LIBRARY_DEBUG)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_SHARED_LIBRARY_DEBUG}")
endif()
elseif(TARGET GLEW::glew_s)
if(GLEW_STATIC_LIBRARY_RELEASE)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_RELEASE "${GLEW_STATIC_LIBRARY_RELEASE}"
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
endif()
if(GLEW_STATIC_LIBRARY_DEBUG AND GLEW_USE_STATIC_LIBS)
set_property(TARGET GLEW::GLEW
APPEND
PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(GLEW::GLEW
PROPERTIES IMPORTED_LOCATION_DEBUG "${GLEW_STATIC_LIBRARY_DEBUG}"
INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC)
endif()
elseif(GLEW_VERBOSE)
message(WARNING "FindGLEW: no `GLEW::glew` or `GLEW::glew_s` target was created. Something went wrong in FindGLEW target creation.")
endif()
endif()
+3
View File
@@ -379,8 +379,10 @@ include(Boost/Boost.cmake)
include(Cereal/Cereal.cmake)
include(Qhull/Qhull.cmake)
include(GLEW/GLEW.cmake)
include(GLFW/GLFW.cmake)
include(OpenCSG/OpenCSG.cmake)
set(SLVS_PKG "")
if (SLIC3R_CAD)
include(SLVS/SLVS.cmake)
@@ -476,6 +478,7 @@ set(_dep_list
dep_Draco
dep_NLopt
dep_OpenVDB
dep_OpenCSG
${SLVS_PKG}
dep_OpenCV
dep_Eigen
+2
View File
@@ -1,4 +1,6 @@
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
)
+14
View File
@@ -0,0 +1,14 @@
# We have to check for OpenGL to compile GLEW
set(OpenGL_GL_PREFERENCE "LEGACY") # to prevent a nasty warning by cmake
find_package(OpenGL QUIET REQUIRED)
orcaslicer_add_cmake_project(
GLEW
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/glew
CMAKE_ARGS
-DGLEW_USE_EGL=OFF
)
if (MSVC)
add_debug_dep(dep_GLEW)
endif ()
+44
View File
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.0)
project(GLEW)
find_package(OpenGL REQUIRED)
# Allow parent project to control EGL usage.
# Default to OFF since OrcaSlicer forces GDK_BACKEND=x11 (using GLX contexts).
# GLEW must use glXGetProcAddressARB (GLX) to match wxWidgets GL canvas.
# Using EGL function loading with GLX contexts causes rendering failures.
option(GLEW_USE_EGL "Use EGL instead of GLX for OpenGL function loading" OFF)
if(GLEW_USE_EGL)
message(STATUS "Building GLEW with EGL support")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGLEW_EGL")
else()
message(STATUS "Building GLEW with GLX support")
endif()
add_library(GLEW src/glew.c)
target_include_directories(GLEW PRIVATE include/)
target_link_libraries(GLEW PUBLIC OpenGL::GL)
if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(GLEW PUBLIC GLEW_STATIC)
endif ()
include(GNUInstallDirs)
install(
FILES
${PROJECT_SOURCE_DIR}/include/GL/glew.h
${PROJECT_SOURCE_DIR}/include/GL/wglew.h
${PROJECT_SOURCE_DIR}/include/GL/glxew.h
${PROJECT_SOURCE_DIR}/include/GL/eglew.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/GL
)
install(TARGETS GLEW GLEW
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
+73
View File
@@ -0,0 +1,73 @@
The OpenGL Extension Wrangler Library
Copyright (C) 2002-2007, Milan Ikits <milan ikits[]ieee org>
Copyright (C) 2002-2007, Marcelo E. Magallon <mmagallo[]debian org>
Copyright (C) 2002, Lev Povalahev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name of the author may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
Mesa 3-D graphics library
Version: 7.0
Copyright (C) 1999-2007 Brian Paul All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copyright (c) 2007 The Khronos Group Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and/or associated documentation files (the
"Materials"), to deal in the Materials without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Materials, and to
permit persons to whom the Materials are furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Materials.
THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+251
View File
@@ -0,0 +1,251 @@
# GLEW - The OpenGL Extension Wrangler Library
The OpenGL Extension Wrangler Library (GLEW) is a cross-platform open-source C/C++ extension loading library. GLEW provides efficient run-time mechanisms for determining which OpenGL extensions are supported on the target platform. OpenGL core and extension functionality is exposed in a single header file. GLEW has been tested on a variety of operating systems, including Windows, Linux, Mac OS X, FreeBSD, Irix, and Solaris.
![](http://glew.sourceforge.net/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
@@ -0,0 +1 @@
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
@@ -0,0 +1,101 @@
cmake_minimum_required(VERSION 3.0)
project(OpenCSG)
if (NOT BUILD_SHARED_LIBS)
set(GLEW_USE_STATIC_LIBS ON)
elseif (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
find_package(OpenGL REQUIRED)
set(GLEW_VERBOSE ON)
find_package(GLEW 1.13.0 REQUIRED)
set(_srcfiles
src/area.cpp
src/batch.cpp
src/context.cpp
src/channelManager.cpp
src/frameBufferObject.cpp
src/frameBufferObjectExt.cpp
src/occlusionQuery.cpp
src/opencsgRender.cpp
src/openglHelper.cpp
src/pBufferTexture.cpp
src/primitive.cpp
src/primitiveHelper.cpp
src/renderGoldfeather.cpp
src/renderSCS.cpp
src/scissorMemo.cpp
src/settings.cpp
src/stencilManager.cpp
RenderTexture/RenderTexture.cpp
include/opencsg.h
src/opencsgConfig.h
src/area.h
src/batch.h
src/context.h
src/channelManager.h
src/frameBufferObject.h
src/frameBufferObjectExt.h
src/occlusionQuery.h
src/offscreenBuffer.h
src/opencsgRender.h
src/openglHelper.h
src/pBufferTexture.h
src/primitiveHelper.h
src/scissorMemo.h
src/settings.h
src/stencilManager.h
)
add_library(opencsg ${_srcfiles})
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_include_directories(opencsg PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>)
target_link_libraries(opencsg PRIVATE GLEW::GLEW OpenGL::GL)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION 1.4.2
COMPATIBILITY AnyNewerVersion
)
install(TARGETS opencsg
EXPORT ${PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
export(EXPORT ${PROJECT_NAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
NAMESPACE ${PROJECT_NAME}:: )
set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}Targets
FILE
"${PROJECT_NAME}Config.cmake"
NAMESPACE
${PROJECT_NAME}::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
${PROJECT_SOURCE_DIR}/include/opencsg.h
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/opencsg
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
)
+17
View File
@@ -0,0 +1,17 @@
orcaslicer_add_cmake_project(OpenCSG
# GIT_REPOSITORY https://github.com/floriankirsch/OpenCSG.git
# GIT_TAG 83e274457b46c9ad11a4ee599203250b1618f3b9 #v1.4.2
URL https://github.com/floriankirsch/OpenCSG/archive/refs/tags/opencsg-1-4-2-release.zip
URL_HASH SHA256=51afe0db79af8386e2027d56d685177135581e0ee82ade9d7f2caff8deab5ec5
PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in ./CMakeLists.txt
DEPENDS dep_GLEW
)
if (TARGET ${ZLIB_PKG})
add_dependencies(dep_OpenCSG ${ZLIB_PKG})
endif()
if (MSVC)
add_debug_dep(dep_OpenCSG)
endif ()
+1 -1
View File
@@ -191,7 +191,7 @@ public:
tail->next = std::move(p);
tail = new_tail;
}
disrupt_wait_for_data();
data_cond.notify_one();
}
void wait_and_pop(T& value)
-172
View File
@@ -1,172 +0,0 @@
# Center of mass markers — High Level Design
## Purpose and scope
The "Center of mass" item of the canvas toolbar menu, in the bottom left corner of the 3D view, marks
where the mass of the plate, of each object instance and of each body of an assembly is centered,
in Prepare and in Preview. It helps judge how parts will rest on the plate, for instance whether a
tall or leaning part could tip. It is a view setting: it changes nothing in the model, the slice or
the project file, and it does not reach plate thumbnails. The choice is kept in the app config as
`show_center_of_mass`, off by default. The assembly view and the Design tab have no markers.
Three kinds of marker share one shape, a sphere whose octants alternate between two colors:
- each plate, black and white, for everything on it;
- each object instance, light blue and white;
- each body of an assembly, red and yellow;
- in Preview, the supports and raft of each object instance, green and black.
A click on a marker opens a box beside it with the weight and volume of what it stands for, where its
center lies in that thing's bounding box and the size of the box, and its moments of inertia about
axes through the center parallel to x, y and z.
An assembly is an object of several parts or with negative volumes. Its bodies are the connected
solids its parts make once united, the bodies the separated infills option centers its infill on
(see separated-infills.md): parts that overlap or touch are one body, parts apart are separate
bodies. An object of one body, and every object of a single part, has no body markers, as its object
marker says it all.
Mass is volume times density, the `filament_density` of the filament that prints it, or 1.245 g/cm³
(`DEFAULT_FILAMENT_DENSITY`) for a filament without one. Prepare has the model only, so it takes each
part as a solid of the density of its filament, the part's own or else its object's. It reads each
filament's density from the filament's selected preset, edits not yet saved included, as slicing does:
the plater's own config holds the values of the filament edited last only. Preview has
what will be printed, so its markers come from the toolpaths, whose mass depends on walls, infill
and flow as well. There the solid markers are for what is printed up to the top layer the layer
slider shows: for the plate with brim, raft and supports, where the weight rests at that point of
the print; for an object or a body, its own extrusions. Each has a faded twin for the same at the
end of the print, so the slider shows the weight moving toward where it ends, and at the top layer
the two meet. In Prepare the plate has the model alone, as brim, raft and supports exist only once
sliced.
## Prepare: from the meshes
An object of one part takes the mass properties of its mesh at unit density, times its density, from
`its_mass_properties()`, which handles a mesh in a single pass. Each triangle and a fixed vertex of
the mesh span a tetrahedron whose signed volume is `V = a · (b × c) / 6`, with `a`, `b`, `c` taken
relative to that vertex. By the divergence theorem these volumes add up to the volume of a closed
mesh, their volume-weighted centroids to its center of mass, and their second moments
`V (a aᵀ + b bᵀ + c cᵀ + s sᵀ) / 20`, with `s = a + b + c`, to its own. Shells facing inward, such as
a cavity, subtract themselves, and flipping every triangle changes nothing. The sums are kept in
double precision and relative to a vertex of the mesh rather than the origin, which keeps them exact
for meshes far from it. The result keeps the spread of the mass about its center,
`(x - c)(x - c)ᵀ` averaged over the mass, from which the moments of inertia follow.
The CGAL routines that look alike do not compute this. `CGAL::centroid` weighs tetrahedra by their
unsigned volume, so it fails on cavities and on any shell that is not star-shaped from the fan's
apex; over triangles it returns the centroid of the surface, and over points the average of the
vertices, which depends on the tessellation. `CGAL::barycenter` with the signed volumes as weights
gives the same answer, but only after copying every tetrahedron into a vector of weighted points,
and takes two and a half times as long.
A mesh's result is in its own coordinates. Each `GLVolume` maps it to the world with its world
matrix `M`, and weighs it by the volume times the absolute determinant of that matrix: a center of
mass moves with any affine map, and the spread becomes `L S Lᵀ` for the linear part `L` of `M`, so no
mesh is ever transformed. The `GLVolume`'s matrices, rather than the
model's, let the markers follow an object while it is dragged, before the model is updated. Results
are cached by `ModelVolume` id; a `ModelVolume` takes a new id whenever its mesh changes, which is
the rule `reload_scene()` relies on to rebuild a `GLVolume`'s geometry, so a cached result never
outlives its mesh.
An assembly's parts overlap or touch, which the mesh formula would count twice, so `solid_bodies()`
slices them instead, in the object's coordinates. It cuts the height into 500 slabs, 100 while a part
is dragged, slices every part and negative volume at the middle of each slab, unites the parts and
cuts the negative volumes away, and links the islands of neighboring slabs that overlap into bodies
with `connected_bodies()`. Each island adds a prism of the slab's thickness at the density: its
area, and its first and second moments of area, from the same sums over the outline as the area,
with the slab's height for z. Where parts of different densities overlap, the later volume of the object counts, as
slicing clips every part by the parts after it; each part then weighs the region it prints, which is
credited to the island holding it. Each body also keeps the convex hull of its islands and the height
they span, whose corners, once transformed, give its bounding box, tight while the instance turns
about z only. The object is the sum of its bodies, its box that of its parts, as the object's size
shows it, and each plate the sum of the object instances `PartPlateList::find_instance()` puts on it,
so that an instance on no plate counts in none. The bodies are cached by `ModelObject` id with the volumes, types, densities and
transformations they were sliced from.
## Preview: from the toolpaths
`GCodeProcessor` sums the masses while it processes the G-code, in the same pass that builds the
moves, and leaves them in `GCodeProcessorResult`; `GCodeViewer` keeps a copy of them when it loads a
result. Nothing is stored per move.
Each extrusion weighs the volume of filament its E extrudes times the density of the filament that
extrudes it, so a print of several materials weighs each as it is. Flow ratio, line widths, ironing
and purging into infill all count
as printed. Its mass spreads evenly along the segment the bead's center runs, half the layer height
below the nozzle, in the frame of the stored moves: plate offset added, Z offset removed. Such a
segment from `a` to `b` adds `m (a + b) / 2` to the moments and `m (a² + a b + b²) / 3` to the second
moments along each axis, and its box widened by half the bead's height to the bounding box; not by
half its width, which the processor only estimates, so that a box runs along the walls' center lines. Arcs are already split into segments by the processor. Walls, infill, top and bottom surfaces, ironing
and gap fill make the parts. The brim and the support roles, the raft among them, count only in what
the plate prints. The skirt, the prime tower and custom G-code count nowhere.
The plate takes every extrusion, so it needs nothing more. The objects and bodies need the sliced
objects, which the G-code does not describe, so the G-code export hands the processor a locator
built from the `Print`; G-code opened from a file, or from a project sliced earlier, has no `Print`
behind it, and so shows the plate alone. Object labels would not do: profiles turn the four kinds
Orca writes on and off in every combination, and none of them tells the bodies apart.
The locator numbers the object instances and, for each assembly, the bodies of every instance. It
takes the bodies `PrintObject::prepare_infill()` found for separated infills, or, when that option
did not need them, links the islands (`Layer::lslices`) of neighboring layers into bodies with the
same `connected_bodies()`. For each extrusion of a part, it finds the first layer printed at or above
its height, as spiral vase rises through each layer, and the island holding it with an
`IslandLocator`, the one `solid_bodies()` credits its regions with: by the island's box, widened by
1 mm for walls reaching past it, with a polygon test only where boxes overlap, and the nearest
outline where none holds the point. The boxes of one layer's islands say nothing of the other
instances, so an instance whose widened box reaches another's, as copies placed side by side do,
tests the outlines alone, and outside them the nearest outline of all such instances wins. The island
gives both the instance and the body. The island found last is tried first, as extrusions mostly follow each other on one
island. Brim, raft and supports lie outside the islands. The brim counts in the plate only; a support
or raft extrusion goes to the instance whose footprint, the box of its widened islands, holds it, the
one whose center is nearest among several, or else the nearest footprint, as supports stand below and
around their object.
Each mass holds, for each layer id, the running total of what is printed up to that layer, the last
of which is the faded marker's, so the solid marker for any slider position is a single lookup. The layer ids are those
the moves carry, which are also the layers of libvgcode and of the slider; in a print by object they
follow the order of printing, so the solid markers show the objects printed so far as they are.
## Drawing
`smooth_sphere()` with a resolution divisible by four leaves every triangle within one octant, so it
splits into two models drawn with the `gouraud_light` shader in each kind's two colors. The radius is
9 pixels for the plate, 7 for the objects, 6 for the supports and 5 for the bodies, scaled like the canvas toolbar for the
display's DPI and kept constant on screen through the camera's inverse zoom. They are drawn in that
order, so that markers at one place show as rings. The faded markers are the same spheres at 40%
opacity, drawn before all the solid ones, which show over them where both meet.
The centers usually lie inside the objects, so the markers are drawn without the depth test and show
through the objects and anything in front of them. Back face culling keeps the far half of a sphere
from covering the near one. They are drawn after the ambient occlusion pass, which would otherwise
darken them as the surface behind them, and before FXAA, which smooths their edges.
The markers are part of the cached scene, so toggling them, or changing a filament's density while
they are shown, marks the scene dirty, and moving the layer slider redraws the scene with the solid
markers where they belong. In Prepare they are hidden while any gizmo other than Move, Rotate, Scale
and Lay on face is open, since the others work on the surface a marker would cover, and a hidden
object has no markers.
## Details
The markers drawn last are kept, and a left click is tested against them before it selects: each
center and a point a radius to its right are projected to the screen, and the click hits a marker
within that distance. The solid markers are tested before the faded ones and the smaller kinds
before the larger, the order in which they cover each other. A hit opens the details of that marker
and keeps the click from changing the selection; a click anywhere else closes them. The box is an
ImGui window beside the marker, redrawn with the overlay from the markers of the last scene, so it
follows a dragged object, and in Preview the layer slider. It closes when its marker is gone, or when
the number of markers of its kind changes, as then it may stand for something else.
Its title says what the marker stands for: the plate, an object, an assembly or a part, the body of
an assembly. The G-code export lists the object instances for the processor, marking assemblies, as
it hands it the locator.
Each marker carries its sums: mass, volume, first moments and the second moments about the origin
along each axis, `Σ m x²`, `Σ m y²` and `Σ m z²`, which add up from parts to objects to plates. The
moment of inertia about the axis through the center parallel to x is then
`m (σy² + σz²)`, with `σ² = Σ m x² / m - c²` along each axis, and likewise for y and z. Masses are
kept in mg, volume times density in g/cm³, and shown in g, volumes in cm³ and moments of inertia in
g·mm². In Preview the box tells the finished print from what is printed up to the layer shown, the
two weighing differently, and both are placed in the bounding box of everything the marker holds
by the end.
+2 -2
View File
@@ -18,8 +18,8 @@ each body on its own (see Octree infill).
## Bodies
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
into 3D connected bodies with `connected_bodies()` before bridges are detected,
so bridge anchors and printed infill share one origin. Islands on adjacent layers belong to one body
into 3D connected bodies before bridges are detected, 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
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
+1
View File
@@ -2,4 +2,5 @@
#add_subdirectory(openvdb)
# add_subdirectory(meshboolean)
add_subdirectory(its_neighbor_index)
# add_subdirectory(opencsg)
#add_subdirectory(aabb-evaluation)
+30
View File
@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.0)
project(OpenCSG-example)
add_executable(opencsg_example WIN32
main.cpp
Engine.hpp Engine.cpp
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/Jobs/Job.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
find_package(OpenCSG REQUIRED)
include(${wxWidgets_USE_FILE})
target_link_libraries(opencsg_example libslic3r)
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
OpenCSG::opencsg
GLEW::GLEW
OpenGL::GL
#-lXrandr -lXext -lX11
)
+495
View File
@@ -0,0 +1,495 @@
#include "Engine.hpp"
#include <libslic3r/Utils.hpp>
#include <libslic3r/SLAPrint.hpp>
#include <GL/glew.h>
#include <boost/log/trivial.hpp>
#ifndef NDEBUG
#define HAS_GLSAFE
#endif
#ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
{
GLenum err = glGetError();
if (err == GL_NO_ERROR)
return;
const char *sErr = 0;
switch (err) {
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
default: sErr = "Unknown"; break;
}
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
assert(false);
}
#else
inline void glAssertRecentCall() { }
#define glsafe(cmd) cmd
#define glcheck()
#endif
namespace Slic3r { namespace GL {
Scene::Scene() = default;
Scene::~Scene() = default;
void CSGDisplay::render_scene()
{
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
glsafe(::glColor4fv(color));
if (m_csgsettings.is_enabled()) {
OpenCSG::render(m_scene_cache.primitives_csg);
glDepthFunc(GL_EQUAL);
}
for (auto& p : m_scene_cache.primitives_csg) p->render();
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
for (auto& p : m_scene_cache.primitives_free) p->render();
glFlush();
}
void Scene::set_print(std::unique_ptr<SLAPrint> &&print)
{
m_print = std::move(print);
// Notify displays
call(&Listener::on_scene_updated, m_listeners, *this);
}
BoundingBoxf3 Scene::get_bounding_box() const
{
return m_print->model().bounding_box();
}
void CSGDisplay::SceneCache::clear()
{
primitives_csg.clear();
primitives_free.clear();
primitives.clear();
}
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
{
auto p = std::make_shared<Primitive>();
p->load_mesh(mesh);
primitives.emplace_back(p);
primitives_free.emplace_back(p.get());
return p;
}
std::shared_ptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
OpenCSG::Operation o,
unsigned c)
{
auto p = std::make_shared<Primitive>(o, c);
p->load_mesh(mesh);
primitives.emplace_back(p);
primitives_csg.emplace_back(p.get());
return p;
}
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
{
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
this->vertices_and_normals_interleaved.emplace_back(nx);
this->vertices_and_normals_interleaved.emplace_back(ny);
this->vertices_and_normals_interleaved.emplace_back(nz);
this->vertices_and_normals_interleaved.emplace_back(x);
this->vertices_and_normals_interleaved.emplace_back(y);
this->vertices_and_normals_interleaved.emplace_back(z);
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
}
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
if (this->vertices_and_normals_interleaved_VBO_id != 0)
return;
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
this->triangle_indices.emplace_back(idx1);
this->triangle_indices.emplace_back(idx2);
this->triangle_indices.emplace_back(idx3);
this->triangle_indices_size = this->triangle_indices.size();
}
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
{
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
assert(quad_indices.empty() && triangle_indices_size == 0);
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
int vertices_count = 0;
for (size_t i = 0; i < mesh.facets_count(); ++i) {
const stl_facet &facet = mesh.stl.facet_start[i];
for (int j = 0; j < 3; ++j)
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
vertices_count += 3;
}
}
void IndexedVertexArray::finalize_geometry()
{
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
assert(this->triangle_indices_VBO_id == 0);
assert(this->quad_indices_VBO_id == 0);
if (!this->vertices_and_normals_interleaved.empty()) {
glsafe(
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
this->vertices_and_normals_interleaved_VBO_id));
glsafe(
::glBufferData(GL_ARRAY_BUFFER,
GLsizeiptr(
this->vertices_and_normals_interleaved.size() *
4),
this->vertices_and_normals_interleaved.data(),
GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->vertices_and_normals_interleaved.clear();
}
if (!this->triangle_indices.empty()) {
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->triangle_indices_VBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
GLsizeiptr(this->triangle_indices.size() * 4),
this->triangle_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->triangle_indices.clear();
}
if (!this->quad_indices.empty()) {
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->quad_indices_VBO_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
GLsizeiptr(this->quad_indices.size() * 4),
this->quad_indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->quad_indices.clear();
}
}
void IndexedVertexArray::release_geometry()
{
if (this->vertices_and_normals_interleaved_VBO_id) {
glsafe(
::glDeleteBuffers(1,
&this->vertices_and_normals_interleaved_VBO_id));
this->vertices_and_normals_interleaved_VBO_id = 0;
}
if (this->triangle_indices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
this->triangle_indices_VBO_id = 0;
}
if (this->quad_indices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
this->quad_indices_VBO_id = 0;
}
this->clear();
}
void IndexedVertexArray::render() const
{
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
assert(this->triangle_indices_VBO_id != 0 ||
this->quad_indices_VBO_id != 0);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
this->vertices_and_normals_interleaved_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
reinterpret_cast<const void *>(3 * sizeof(float))));
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
// Render using the Vertex Buffer Objects.
if (this->triangle_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->triangle_indices_VBO_id));
glsafe(::glDrawElements(GL_TRIANGLES,
GLsizei(this->triangle_indices_size),
GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
if (this->quad_indices_size > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
this->quad_indices_VBO_id));
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void IndexedVertexArray::clear() {
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
vertices_and_normals_interleaved_size = 0;
triangle_indices_size = 0;
quad_indices_size = 0;
}
void IndexedVertexArray::shrink_to_fit() {
this->vertices_and_normals_interleaved.shrink_to_fit();
this->triangle_indices.shrink_to_fit();
this->quad_indices.shrink_to_fit();
}
void Volume::render()
{
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
m_geom.render();
glsafe(::glPopMatrix());
}
void Display::clear_screen()
{
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
Display::~Display()
{
OpenCSG::freeResources();
}
void Display::set_active(long width, long height)
{
if (!m_initialized) {
glewInit();
m_initialized = true;
}
// gray background
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
// Enable two OpenGL lights
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHTING);
glEnable(GL_NORMALIZE);
// Use depth buffering for hidden surface elimination
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
set_screen_size(width, height);
}
void Display::set_screen_size(long width, long height)
{
if (m_size.x() != width || m_size.y() != height)
m_camera->set_screen(width, height);
m_size = {width, height};
}
void Display::repaint()
{
clear_screen();
m_camera->view();
render_scene();
m_fps_counter.update();
swap_buffers();
}
void Controller::on_scene_updated(const Scene &scene)
{
const SLAPrint *print = scene.get_print();
if (!print) return;
auto bb = scene.get_bounding_box();
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
m_wheel_pos = long(2 * d);
call_cameras(&Camera::set_zoom, m_wheel_pos);
call(&Display::on_scene_updated, m_displays, scene);
}
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
{
m_wheel_pos += v / d;
call_cameras(&Camera::set_zoom, m_wheel_pos);
call(&Display::repaint, m_displays);
}
void Controller::on_moved_to(long x, long y)
{
if (m_left_btn) {
call_cameras(&Camera::rotate, (Vec2i32{x, y} - m_mouse_pos).cast<float>());
call(&Display::repaint, m_displays);
}
m_mouse_pos = {x, y};
}
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
{
using namespace OpenCSG;
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
m_csgsettings = settings;
setOption(AlgorithmSetting, m_csgsettings.get_algo());
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
if (needupdate) {
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
if (p->getConvexity() > 1)
p->setConvexity(m_csgsettings.get_convexity());
}
}
void CSGDisplay::on_scene_updated(const Scene &scene)
{
const SLAPrint *print = scene.get_print();
if (!print) return;
m_scene_cache.clear();
for (const SLAPrintObject *po : print->objects()) {
const ModelObject *mo = po->model_object();
TriangleMesh msh = mo->raw_mesh();
sla::DrainHoles holedata = mo->sla_drain_holes;
for (const ModelInstance *mi : mo->instances) {
TriangleMesh mshinst = msh;
auto interior = po->hollowed_interior_mesh();
interior.transform(po->trafo().inverse());
mshinst.merge(interior);
mi->transform_mesh(&mshinst);
auto bb = mshinst.bounding_box();
auto center = bb.center().cast<float>();
mshinst.translate(-center);
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
m_csgsettings.get_convexity());
}
for (const sla::DrainHole &holept : holedata) {
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
}
}
repaint();
}
void Camera::view()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
m_referene.x(), m_referene.y(), m_referene.z(),
0.0, 0.0, 1.0); /* up is in positive Y direction */
// TODO Could have been set in prevoius gluLookAt in first argument
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
if (m_clip_z > 0.) {
GLdouble plane[] = {0., 0., 1., m_clip_z};
glClipPlane(GL_CLIP_PLANE0, plane);
glEnable(GL_CLIP_PLANE0);
} else {
glDisable(GL_CLIP_PLANE0);
}
}
void PerspectiveCamera::set_screen(long width, long height)
{
// Setup the view of the CSG shape
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, width / double(height), .1, 200.0);
glMatrixMode(GL_MODELVIEW);
}
bool enable_multisampling(bool e)
{
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
GLint is_ms_context;
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
else return false;
}
MouseInput::Listener::~Listener() = default;
void FpsCounter::update()
{
++m_frames;
TimePoint msec = Clock::now();
double seconds_window = to_sec(msec - m_window);
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
if (to_sec(msec - m_last) >= m_resolution) {
m_last = msec;
for (auto &l : m_listeners) l(m_fps);
}
if (seconds_window >= m_window_size) {
m_frames = 0;
m_window = msec;
}
}
}} // namespace Slic3r::GL
+488
View File
@@ -0,0 +1,488 @@
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
#include <vector>
#include <memory>
#include <chrono>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <opencsg/opencsg.h>
namespace Slic3r {
class SLAPrint;
namespace GL {
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
// remove empty weak pointers from a vector
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
auto it = std::remove_if(listeners.begin(), listeners.end(),
[](auto &l) { return !l.lock(); });
listeners.erase(it, listeners.end());
}
// Call a class method on each element of a vector of objects (weak pointers)
// of the same type.
template<class F, class L, class...Args>
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
for (auto &l : listeners)
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
}
// A representation of a mouse input for the engine.
class MouseInput
{
public:
enum WheelAxis { waVertical, waHorizontal };
// Interface to implement if an object wants to receive notifications
// about mouse events.
class Listener {
public:
virtual ~Listener();
virtual void on_left_click_down() {}
virtual void on_left_click_up() {}
virtual void on_right_click_down() {}
virtual void on_right_click_up() {}
virtual void on_double_click() {}
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
virtual void on_moved_to(long /*x*/, long /*y*/) {}
};
private:
vector<std::weak_ptr<Listener>> m_listeners;
public:
virtual ~MouseInput() = default;
virtual void left_click_down()
{
call(&Listener::on_left_click_down, m_listeners);
}
virtual void left_click_up()
{
call(&Listener::on_left_click_up, m_listeners);
}
virtual void right_click_down()
{
call(&Listener::on_right_click_down, m_listeners);
}
virtual void right_click_up()
{
call(&Listener::on_right_click_up, m_listeners);
}
virtual void double_click()
{
call(&Listener::on_double_click, m_listeners);
}
virtual void scroll(long v, long d, WheelAxis wa)
{
call(&Listener::on_scroll, m_listeners, v, d, wa);
}
virtual void move_to(long x, long y)
{
call(&Listener::on_moved_to, m_listeners, x, y);
}
void add_listener(std::shared_ptr<Listener> listener)
{
m_listeners.emplace_back(listener);
cleanup(m_listeners);
}
};
// This is a stripped down version of Slic3r::IndexedVertexArray
class IndexedVertexArray {
public:
~IndexedVertexArray() { release_geometry(); }
// Vertices and their normals, interleaved to be used by void
// glInterleavedArrays(GL_N3F_V3F, 0, x)
vector<float> vertices_and_normals_interleaved;
vector<int> triangle_indices;
vector<int> quad_indices;
// When the geometry data is loaded into the graphics card as Vertex
// Buffer Objects, the above mentioned std::vectors are cleared and the
// following variables keep their original length.
size_t vertices_and_normals_interleaved_size{ 0 };
size_t triangle_indices_size{ 0 };
size_t quad_indices_size{ 0 };
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
unsigned int triangle_indices_VBO_id{ 0 };
unsigned int quad_indices_VBO_id{ 0 };
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
inline void push_geometry(
double x, double y, double z, double nx, double ny, double nz)
{
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
inline void push_geometry(const Vec3d &p, const Vec3d &n)
{
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
}
void push_triangle(int idx1, int idx2, int idx3);
void load_mesh(const TriangleMesh &mesh);
inline bool has_VBOs() const
{
return vertices_and_normals_interleaved_VBO_id != 0;
}
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been
// loaded into the VBOs.
void finalize_geometry();
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
void render() const;
// Is there any geometry data stored?
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
void clear();
// Shrink the internal storage to tighly fit the data stored.
void shrink_to_fit();
};
// Try to enable or disable multisampling.
bool enable_multisampling(bool e = true);
class Volume {
IndexedVertexArray m_geom;
Geometry::Transformation m_trafo;
public:
void render();
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
void scale(double s) { scale({s, s, s}); }
inline void load_mesh(const TriangleMesh &mesh)
{
m_geom.load_mesh(mesh);
m_geom.finalize_geometry();
}
};
// A primitive that can be used with OpenCSG rendering algorithms.
// Does a similar job to GLVolume.
class Primitive : public Volume, public OpenCSG::Primitive
{
public:
using OpenCSG::Primitive::Primitive;
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
void render() override { Volume::render(); }
};
// A simple representation of a camera in a 3D scene
class Camera {
protected:
Vec2f m_rot = {0., 0.};
Vec3d m_referene = {0., 0., 0.};
double m_zoom = 0.;
double m_clip_z = 0.;
public:
virtual ~Camera() = default;
virtual void view();
virtual void set_screen(long width, long height) = 0;
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
void rotate(const Vec2f &rotation) { m_rot += rotation; }
void set_zoom(double z) { m_zoom = z; }
void set_reference_point(const Vec3d &p) { m_referene = p; }
void set_clip_z(double z) { m_clip_z = z; }
};
// Reset a camera object
inline void reset(Camera &cam)
{
cam.set_rotation({0., 0.});
cam.set_zoom(0.);
cam.set_reference_point({0., 0., 0.});
cam.set_clip_z(0.);
}
// Specialization of a camera which shows in perspective projection
class PerspectiveCamera: public Camera {
public:
void set_screen(long width, long height) override;
};
// A simple counter of FPS. Subscribed objects will receive updates of the
// current fps.
class FpsCounter {
vector<std::function<void(double)>> m_listeners;
using Clock = std::chrono::high_resolution_clock;
using Duration = Clock::duration;
using TimePoint = Clock::time_point;
int m_frames = 0;
TimePoint m_last = Clock::now(), m_window = m_last;
double m_resolution = 0.1, m_window_size = 1.0;
double m_fps = 0.;
static double to_sec(Duration d)
{
return d.count() * double(Duration::period::num) / Duration::period::den;
}
public:
void update();
void add_listener(std::function<void(double)> lst)
{
m_listeners.emplace_back(lst);
}
void clear_listeners() { m_listeners = {}; }
void set_notification_interval(double seconds);
void set_measure_window_size(double seconds);
double get_notification_interval() const { return m_resolution; }
double get_mesure_window_size() const { return m_window_size; }
};
// Collection of the used OpenCSG library settings.
class CSGSettings {
public:
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
private:
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
bool m_enable = true;
unsigned int m_convexity = DEFAULT_CONVEXITY;
public:
int get_algo() const { return int(m_csgalg); }
void set_algo(int alg)
{
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
m_csgalg = OpenCSG::Algorithm(alg);
}
int get_depth_algo() const { return int(m_depth_algo); }
void set_depth_algo(int alg)
{
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
}
int get_optimization() const { return int(m_optim); }
void set_optimization(int o)
{
if (o < OpenCSG::Optimization::OptimizationUnused)
m_optim = OpenCSG::Optimization(o);
}
void enable_csg(bool en = true) { m_enable = en; }
bool is_enabled() const { return m_enable; }
unsigned get_convexity() const { return m_convexity; }
void set_convexity(unsigned c) { m_convexity = c; }
};
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
class Scene
{
std::unique_ptr<SLAPrint> m_print;
public:
// Subscribers will be notified if the model is changed. This might be a
// display which will have to load the meshes and repaint itself when
// the scene data changes.
// eg. We load a new 3mf through the UI, this will notify the controller
// associated with the scene and all the displays that the controller is
// connected with.
class Listener {
public:
virtual ~Listener() = default;
virtual void on_scene_updated(const Scene &scene) = 0;
};
Scene();
~Scene();
void set_print(std::unique_ptr<SLAPrint> &&print);
const SLAPrint * get_print() const { return m_print.get(); }
BoundingBoxf3 get_bounding_box() const;
void add_listener(std::shared_ptr<Listener> listener)
{
m_listeners.emplace_back(listener);
cleanup(m_listeners);
}
private:
vector<std::weak_ptr<Listener>> m_listeners;
};
// The basic Display. This is almost just an interface but will do all the
// initialization and show the fps values. Overriding the render_scene is
// needed to show the scene content. The specific method of displaying the
// scene is up the particular implementation (OpenCSG or other screen space
// boolean algorithms)
class Display : public Scene::Listener
{
protected:
Vec2i32 m_size;
bool m_initialized = false;
std::shared_ptr<Camera> m_camera;
FpsCounter m_fps_counter;
public:
explicit Display(std::shared_ptr<Camera> camera = nullptr)
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
{}
~Display() override;
std::shared_ptr<const Camera> get_camera() const { return m_camera; }
std::shared_ptr<Camera> get_camera() { return m_camera; }
void set_camera(std::shared_ptr<Camera> cam) { m_camera = cam; }
virtual void swap_buffers() = 0;
virtual void set_active(long width, long height);
virtual void set_screen_size(long width, long height);
Vec2i32 get_screen_size() const { return m_size; }
virtual void repaint();
bool is_initialized() const { return m_initialized; }
virtual void clear_screen();
virtual void render_scene() {}
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
{
m_fps_counter = std::forward<_FpsCounter>(fpsc);
}
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
FpsCounter &get_fps_counter() { return m_fps_counter; }
};
// Special dispaly using OpenCSG for rendering the scene.
class CSGDisplay : public Display {
protected:
CSGSettings m_csgsettings;
// Cache the renderable primitives. These will be fetched when the scene
// is modified.
struct SceneCache {
vector<std::shared_ptr<Primitive>> primitives;
vector<Primitive *> primitives_free;
vector<OpenCSG::Primitive *> primitives_csg;
void clear();
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh);
std::shared_ptr<Primitive> add_mesh(const TriangleMesh &mesh,
OpenCSG::Operation op,
unsigned covexity);
} m_scene_cache;
public:
// Receive or apply the new settings.
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
void apply_csgsettings(const CSGSettings &settings);
void render_scene() override;
void on_scene_updated(const Scene &scene) override;
};
// The controller is a hub which dispatches mouse events to the connected
// displays. It keeps track of the mouse wheel position, the states whether
// the mouse is being held, dragged, etc... All the connected displays will
// mirror the camera movement (if there is more than one display).
class Controller : public std::enable_shared_from_this<Controller>,
public MouseInput::Listener,
public Scene::Listener
{
long m_wheel_pos = 0;
Vec2i32 m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
bool m_left_btn = false, m_right_btn = false;
std::shared_ptr<Scene> m_scene;
vector<std::weak_ptr<Display>> m_displays;
// Call a method of Camera on all the cameras of the attached displays
template<class F, class...Args>
void call_cameras(F &&f, Args&&... args) {
for (std::weak_ptr<Display> &l : m_displays)
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
(cam.get()->*f)(std::forward<Args>(args)...);
}
public:
// Set the scene that will be controlled.
void set_scene(std::shared_ptr<Scene> scene)
{
m_scene = scene;
m_scene->add_listener(shared_from_this());
}
const Scene * get_scene() const { return m_scene.get(); }
void add_display(std::shared_ptr<Display> disp)
{
m_displays.emplace_back(disp);
cleanup(m_displays);
}
void remove_displays() { m_displays = {}; }
void on_scene_updated(const Scene &scene) override;
void on_left_click_down() override { m_left_btn = true; }
void on_left_click_up() override { m_left_btn = false; }
void on_right_click_down() override { m_right_btn = true; }
void on_right_click_up() override { m_right_btn = false; }
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
void on_moved_to(long x, long y) override;
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
};
}} // namespace Slic3r::GL
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
+63
View File
@@ -0,0 +1,63 @@
#include "ShaderCSGDisplay.hpp"
#include "libslic3r/SLAPrint.hpp"
#include <GL/glew.h>
namespace Slic3r { namespace GL {
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
{
auto v = std::make_shared<CSGVolume>();
v->load_mesh(mesh);
m_volumes.emplace_back(v);
}
void ShaderCSGDisplay::render_scene()
{
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
glColor4fv(color);
glDepthFunc(GL_LESS);
for (auto &v : m_volumes) v->render();
glFlush();
}
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
{
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
// Look at CSGDisplay::on_scene_updated to see how its done there.
const SLAPrint *print = scene.get_print();
if (!print) return;
m_volumes.clear();
for (const SLAPrintObject *po : print->objects()) {
const ModelObject *mo = po->model_object();
TriangleMesh msh = mo->raw_mesh();
sla::DrainHoles holedata = mo->sla_drain_holes;
for (const ModelInstance *mi : mo->instances) {
TriangleMesh mshinst = msh;
auto interior = po->hollowed_interior_mesh();
interior.transform(po->trafo().inverse());
mshinst.merge(interior);
mi->transform_mesh(&mshinst);
auto bb = mshinst.bounding_box();
auto center = bb.center().cast<float>();
mshinst.translate(-center);
add_mesh(mshinst);
}
for (const sla::DrainHole &holept : holedata)
add_mesh(sla::to_triangle_mesh(holept.to_mesh()));
}
repaint();
}
}} // namespace Slic3r::GL
+27
View File
@@ -0,0 +1,27 @@
#ifndef SHADERCSGDISPLAY_HPP
#define SHADERCSGDISPLAY_HPP
#include "Engine.hpp"
namespace Slic3r { namespace GL {
class CSGVolume: public Volume
{
// Extend...
};
class ShaderCSGDisplay: public Display {
protected:
vector<std::shared_ptr<CSGVolume>> m_volumes;
void add_mesh(const TriangleMesh &mesh);
public:
void render_scene() override;
void on_scene_updated(const Scene &scene) override;
};
}}
#endif // SHADERCSGDISPLAY_HPP
+734
View File
@@ -0,0 +1,734 @@
#include <iostream>
#include <utility>
#include <memory>
#include "Engine.hpp"
#include "ShaderCSGDisplay.hpp"
#include <GL/glew.h>
#include <opencsg/opencsg.h>
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/slider.h>
#include <wx/tglbtn.h>
#include <wx/combobox.h>
#include <wx/spinctrl.h>
#include <wx/msgdlg.h>
#include <wx/glcanvas.h>
#include <wx/cmdline.h>
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "slic3r/GUI/Jobs/Job.hpp"
#include "slic3r/GUI/ProgressStatusBar.hpp"
using namespace Slic3r::GL;
class Renderer {
protected:
wxGLCanvas *m_canvas;
std::shared_ptr<wxGLContext> m_context;
public:
Renderer(wxGLCanvas *c): m_canvas{c} {
auto ctx = new wxGLContext(m_canvas);
if (!ctx || !ctx->IsOK()) {
wxMessageBox("Could not create OpenGL context.", "Error",
wxOK | wxICON_ERROR);
return;
}
m_context.reset(ctx);
}
wxGLContext * context() { return m_context.get(); }
const wxGLContext * context() const { return m_context.get(); }
};
// Tell the CSGDisplay how to swap buffers and set the gl context.
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
public:
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
void set_active(long w, long h) override
{
m_canvas->SetCurrent(*m_context);
Slic3r::GL::Display::set_active(w, h);
}
void swap_buffers() override { m_canvas->SwapBuffers(); }
};
// Tell the CSGDisplay how to swap buffers and set the gl context.
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
public:
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
void set_active(long w, long h) override
{
m_canvas->SetCurrent(*m_context);
Slic3r::GL::Display::set_active(w, h);
}
void swap_buffers() override { m_canvas->SwapBuffers(); }
};
// The opengl rendering facility. Here we implement the rendering objects.
class Canvas: public wxGLCanvas
{
// One display is active at a time, the OCSGRenderer by default.
std::shared_ptr<Slic3r::GL::Display> m_display;
public:
template<class...Args>
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
std::shared_ptr<Slic3r::GL::Display> get_display() const { return m_display; }
void set_display(std::shared_ptr<Slic3r::GL::Display> d) { m_display = d; }
};
// Enumerate possible mouse events, we will record them.
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
struct Event
{
EEvents type;
long a, b;
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
};
// Create a special mouse input adapter, which can store (record) the received
// mouse signals into a file and play back the stored events later.
class RecorderMouseInput: public MouseInput {
std::vector<Event> m_events;
bool m_recording = false, m_playing = false;
public:
void left_click_down() override
{
if (m_recording) m_events.emplace_back(LCLK_D);
if (!m_playing) MouseInput::left_click_down();
}
void left_click_up() override
{
if (m_recording) m_events.emplace_back(LCLK_U);
if (!m_playing) MouseInput::left_click_up();
}
void right_click_down() override
{
if (m_recording) m_events.emplace_back(RCLK_D);
if (!m_playing) MouseInput::right_click_down();
}
void right_click_up() override
{
if (m_recording) m_events.emplace_back(RCLK_U);
if (!m_playing) MouseInput::right_click_up();
}
void double_click() override
{
if (m_recording) m_events.emplace_back(DDCLK);
if (!m_playing) MouseInput::double_click();
}
void scroll(long v, long d, WheelAxis wa) override
{
if (m_recording) m_events.emplace_back(SCRL, v, d);
if (!m_playing) MouseInput::scroll(v, d, wa);
}
void move_to(long x, long y) override
{
if (m_recording) m_events.emplace_back(MV, x, y);
if (!m_playing) MouseInput::move_to(x, y);
}
void save(std::ostream &stream)
{
for (const Event &evt : m_events)
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
}
void load(std::istream &stream)
{
m_events.clear();
while (stream.good()) {
int type; long a, b;
stream >> type >> a >> b;
m_events.emplace_back(EEvents(type), a, b);
}
}
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
void play()
{
m_playing = true;
for (const Event &evt : m_events) {
switch (evt.type) {
case LCLK_U: MouseInput::left_click_up(); break;
case LCLK_D: MouseInput::left_click_down(); break;
case RCLK_U: MouseInput::right_click_up(); break;
case RCLK_D: MouseInput::right_click_down(); break;
case DDCLK: MouseInput::double_click(); break;
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
case MV: MouseInput::move_to(evt.a, evt.b); break;
}
wxTheApp->Yield();
if (!m_playing)
break;
}
m_playing = false;
}
void stop() { m_playing = false; }
bool is_playing() const { return m_playing; }
};
// The top level frame of the application.
class MyFrame: public wxFrame
{
// Instantiate the 3D engine.
std::shared_ptr<Scene> m_scene; // Model
std::shared_ptr<Canvas> m_canvas; // Views store
std::shared_ptr<OCSGRenderer> m_ocsgdisplay; // View
std::shared_ptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
std::shared_ptr<Controller> m_ctl; // Controller
// Add a status bar with progress indication.
std::shared_ptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
RecorderMouseInput m_mouse;
// When loading a Model from 3mf and preparing it, we use a separate thread.
class SLAJob: public Slic3r::GUI::Job {
MyFrame *m_parent;
std::unique_ptr<Slic3r::SLAPrint> m_print;
std::string m_fname;
public:
SLAJob(MyFrame *frame, const std::string &fname)
: Slic3r::GUI::Job{frame->m_stbar}
, m_parent{frame}
, m_fname{fname}
{}
// Runs in separate thread
void process() override;
const std::string & get_project_fname() const { return m_fname; }
protected:
// Runs in the UI thread.
void finalize() override
{
m_parent->m_scene->set_print(std::move(m_print));
m_parent->m_stbar->set_status_text(
wxString::Format("Model %s loaded.", m_fname));
}
};
std::unique_ptr<SLAJob> m_ui_job;
// To keep track of the running average of measured fps values.
double m_fps_avg = 0.;
// We need the record button across methods
wxToggleButton *m_record_btn;
wxComboBox * m_alg_select;
wxComboBox * m_depth_select;
wxComboBox * m_optimization_select;
wxSpinCtrl * m_convexity_spin;
wxToggleButton *m_csg_toggle;
wxToggleButton *m_ms_toggle;
wxStaticText *m_fpstext;
CSGSettings m_csg_settings;
void read_csg_settings(const wxCmdLineParser &parser);
void set_renderer_algorithm(const wxString &alg);
void activate_canvas_display();
public:
MyFrame(const wxString & title,
const wxPoint & pos,
const wxSize & size,
const wxCmdLineParser &parser);
// Grab a 3mf and load (hollow it out) within the UI job.
void load_model(const std::string &fname) {
m_ui_job = std::make_unique<SLAJob>(this, fname);
m_ui_job->start();
}
// Load a previously stored mouse event log and play it back.
void play_back_mouse(const std::string &events_fname)
{
std::fstream stream(events_fname, std::fstream::in);
if (stream.good()) {
std::string model_name;
std::getline(stream, model_name);
load_model(model_name);
while (!m_ui_job->is_finalized())
wxTheApp->Yield();;
int w, h;
stream >> w >> h;
SetSize(w, h);
m_mouse.load(stream);
if (m_record_btn) m_record_btn->Disable();
m_mouse.play();
}
}
Canvas * canvas() { return m_canvas.get(); }
const Canvas * canvas() const { return m_canvas.get(); }
// Bind the canvas mouse events to a class implementing MouseInput interface
void bind_canvas_events(MouseInput &msinput);
double get_fps_average() const { return m_fps_avg; }
};
// Possible OpenCSG configuration values. Will be used on the command line and
// on the UI widgets.
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
{
auto it = std::find(v.begin(), v.end(), a.ToStdString());
return it - v.begin();
};
class App : public wxApp {
MyFrame *m_frame = nullptr;
wxString m_fname;
public:
bool OnInit() override {
wxCmdLineParser parser(argc, argv);
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
parser.Parse();
bool is_play = parser.Found("play", &m_fname);
m_frame = new MyFrame("OrcaSlicer OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
if (is_play) {
Bind(wxEVT_IDLE, &App::Play, this);
m_frame->Show( true );
} else m_frame->Show( true );
return true;
}
void Play(wxIdleEvent &) {
Unbind(wxEVT_IDLE, &App::Play, this);
m_frame->play_back_mouse(m_fname.ToStdString());
m_frame->Destroy();
}
};
wxIMPLEMENT_APP(App);
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
{
wxString alg;
parser.Found("algorithm", &alg);
wxString depth;
parser.Found("depth", &depth);
wxString opt;
parser.Found("optimization", &opt);
long convexity = 1;
parser.Found("convexity", &convexity);
bool csg_off = parser.Found("disable-csg");
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
m_csg_settings.set_convexity(unsigned(convexity));
m_csg_settings.enable_csg(!csg_off);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
}
void MyFrame::set_renderer_algorithm(const wxString &alg)
{
long alg_idx = get_idx(alg, CSG_ALGS);
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
// If there is a valid display in place, save its camera.
auto cam = m_canvas->get_display() ?
m_canvas->get_display()->get_camera() : nullptr;
if (alg == "EnricoShader") {
m_alg_select->SetSelection(int(alg_idx));
m_depth_select->Disable();
m_optimization_select->Disable();
m_csg_toggle->Disable();
m_ocsgdisplay.reset();
canvas()->set_display(nullptr);
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
canvas()->set_display(m_shadercsg_display);
} else {
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
m_alg_select->SetSelection(m_csg_settings.get_algo());
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
m_optimization_select->Enable();
m_csg_toggle->Enable();
m_shadercsg_display.reset();
canvas()->set_display(nullptr);
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
canvas()->set_display(m_ocsgdisplay);
}
if (cam)
m_canvas->get_display()->set_camera(cam);
m_ctl->remove_displays();
m_ctl->add_display(m_canvas->get_display());
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
});
if (IsShown()) {
activate_canvas_display();
m_canvas->get_display()->on_scene_updated(*m_scene);
}
}
void MyFrame::activate_canvas_display()
{
const wxSize ClientSize = m_canvas->GetClientSize();
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
enable_multisampling(m_ms_toggle->GetValue());
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
// This is required even though dc is not used otherwise.
wxPaintDC dc(m_canvas.get());
const wxSize csize = m_canvas->GetClientSize();
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
m_canvas->get_display()->repaint();
});
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
const wxSize csize = m_canvas->GetClientSize();
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
m_canvas->get_display()->repaint();
});
// Do the repaint continuously
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
m_canvas->get_display()->repaint();
evt.RequestMore();
});
bind_canvas_events(m_mouse);
}
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
const wxCmdLineParser &parser):
wxFrame(nullptr, wxID_ANY, title, pos, size)
{
wxMenu *menuFile = new wxMenu;
menuFile->Append(wxID_OPEN);
menuFile->Append(wxID_EXIT);
wxMenuBar *menuBar = new wxMenuBar;
menuBar->Append( menuFile, "&File" );
SetMenuBar( menuBar );
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
m_stbar->embed(this);
SetStatusText( "Welcome to wxWidgets!" );
int attribList[] =
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
// RGB channels each should be allocated with 8 bit depth. One
// should almost certainly get these bit depths by default.
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
// drivers would most likely work with some alpha plane, but
// glReadPixels would not return the alpha channel on NVIDIA if
// not requested when the GL context is created.
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
m_scene = std::make_shared<Scene>();
m_ctl = std::make_shared<Controller>();
m_ctl->set_scene(m_scene);
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
wxDefaultPosition, wxDefaultSize,
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
read_csg_settings(parser);
wxPanel *control_panel = new wxPanel(this);
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
auto console_sizer = new wxBoxSizer(wxVERTICAL);
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
wxDefaultPosition, wxDefaultSize,
wxSL_VERTICAL);
slider_sizer->Add(slider, 1, wxEXPAND);
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
m_csg_toggle->SetValue(true);
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
auto add_combobox = [control_panel, console_sizer]
(const wxString &label, const std::vector<wxString> &list)
{
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
wxDefaultPosition, wxDefaultSize,
int(list.size()), list.data());
auto sz = new wxBoxSizer(wxHORIZONTAL);
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
wxALL | wxALIGN_CENTER, 5);
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
console_sizer->Add(sz, 0, wxEXPAND);
return widget;
};
auto add_spinctl = [control_panel, console_sizer]
(const wxString &label, int initial, int min, int max)
{
auto widget = new wxSpinCtrl(
control_panel, wxID_ANY,
wxString::Format("%d", initial),
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
initial);
auto sz = new wxBoxSizer(wxHORIZONTAL);
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
wxALL | wxALIGN_CENTER, 5);
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
console_sizer->Add(sz, 0, wxEXPAND);
return widget;
};
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
m_optimization_select = add_combobox("Optimization", CSG_OPT);
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
console_sizer->Add(m_fpstext, 0, wxALL, 5);
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
controlsizer->Add(slider_sizer, 0, wxEXPAND);
controlsizer->Add(console_sizer, 1, wxEXPAND);
control_panel->SetSizer(controlsizer);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_canvas.get(), 1, wxEXPAND);
sizer->Add(control_panel, 0, wxEXPAND);
SetSizer(sizer);
wxString alg;
if (!parser.Found("algorithm", &alg)) alg = "Auto";
set_renderer_algorithm(alg);
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
if (m_canvas) RemoveChild(m_canvas.get());
m_canvas.reset();
if (!m_mouse.is_playing()) evt.Skip();
else m_mouse.stop();
});
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
wxFileDialog dlg(this, "Select project file", wxEmptyString,
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
}, wxID_OPEN);
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
activate_canvas_display();
});
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
m_ctl->move_clip_plane(double(slider->GetValue()));
});
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
enable_multisampling(m_ms_toggle->GetValue());
m_canvas->get_display()->repaint();
});
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
stt.enable_csg(m_csg_toggle->GetValue());
m_ocsgdisplay->apply_csgsettings(stt);
});
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
wxString alg = m_alg_select->GetValue();
int sel = m_alg_select->GetSelection();
m_csg_settings.set_algo(sel);
set_renderer_algorithm(alg);
});
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
int sel = m_depth_select->GetSelection();
m_csg_settings.set_depth_algo(sel);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
});
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
int sel = m_optimization_select->GetSelection();
m_csg_settings.set_optimization(sel);
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
});
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
int c = m_convexity_spin->GetValue();
if (c > 0) {
m_csg_settings.set_convexity(unsigned(c));
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
}
});
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
if (!m_ui_job) {
m_stbar->set_status_text("No project loaded!");
return;
}
if (m_record_btn->GetValue()) {
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
m_ctl->on_scene_updated(*m_scene);
m_mouse.record(true);
} else {
m_mouse.record(false);
wxFileDialog dlg(this, "Select output file",
wxEmptyString, wxEmptyString, "*.events",
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
if (dlg.ShowModal() == wxID_OK) {
std::fstream stream(dlg.GetPath().ToStdString(),
std::fstream::out);
if (stream.good()) {
stream << m_ui_job->get_project_fname() << "\n";
wxSize winsize = GetSize();
stream << winsize.x << " " << winsize.y << "\n";
m_mouse.save(stream);
}
}
}
});
}
void MyFrame::bind_canvas_events(MouseInput &ms)
{
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
Slic3r::GL::MouseInput::waVertical :
Slic3r::GL::MouseInput::waHorizontal);
});
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
});
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
ms.right_click_down();
});
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
ms.right_click_up();
});
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
ms.left_click_down();
});
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
ms.left_click_up();
});
ms.add_listener(m_ctl);
}
void MyFrame::SLAJob::process()
{
using SlStatus = Slic3r::PrintBase::SlicingStatus;
Slic3r::DynamicPrintConfig cfg;
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
m_print = std::make_unique<Slic3r::SLAPrint>();
m_print->apply(model, cfg);
Slic3r::PrintBase::TaskParams params;
params.to_object_step = Slic3r::slaposHollowing;
m_print->set_task(params);
m_print->set_status_callback([this](const SlStatus &status) {
update_status(status.percent, status.text);
});
try {
m_print->process();
} catch(std::exception &e) {
update_status(0, wxString("Exception during processing: ") + e.what());
}
}
//int main() {}
+4
View File
@@ -23,11 +23,15 @@ RUN apt-get update && apt-get install -y \
libcairo2-dev \
libcurl4-openssl-dev \
libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \
libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-good1.0-dev \
libgtk-3-dev \
libgtk-3-dev \
libsecret-1-dev \
libsoup2.4-dev \
libssl3 \
+2
View File
@@ -31,6 +31,8 @@ RUN apt-get update && apt-get install -y \
libcairo2-dev \
libcurl4-openssl-dev \
libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \
libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \
@@ -55,6 +55,21 @@ modules:
url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.8.tar.xz
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
buildsystem: cmake-ninja
sources:
@@ -202,6 +217,12 @@ modules:
sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a
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)
- type: file
url: https://github.com/JacobStoren/SolveSpaceLib/archive/4d8704523e4bf212fadf5189f92484244f670fea.zip
+1
View File
@@ -11,6 +11,7 @@ export REQUIRED_DEV_PACKAGES=(
file
gettext
git
glew
gst-plugins-good
gstreamer
gtk3
+1
View File
@@ -11,6 +11,7 @@ export REQUIRED_DEV_PACKAGES=(
file
gettext
git
glew
gst-plugins-good
gstreamer
gtk3
+2 -1
View File
@@ -6,9 +6,10 @@ export REQUIRED_BUNDLES=(
c-basic
dev-utils
devpkg-curl
devpkg-glew
devpkg-glu
devpkg-gstreamer
devpkg-gtk3
devpkg-libglvnd
devpkg-libmspack
devpkg-libsecret
devpkg-openssl
+1 -1
View File
@@ -14,7 +14,7 @@ REQUIRED_DEV_PACKAGES=(
gstreamer1.0-gtk3
libcurl4-openssl-dev
libdbus-1-dev
libgl-dev
libglew-dev
libgstreamerd-3-dev
libgtk-3-dev
libmspack-dev
+1 -1
View File
@@ -22,7 +22,7 @@ REQUIRED_DEV_PACKAGES=(
libspnav-devel
libtool
m4
mesa-libGL-devel
mesa-libGLU-devel
ninja-build
openssl-devel
perl-FindBin
+1 -1
View File
@@ -18,6 +18,7 @@ REQUIRED_DEV_PACKAGES=(
dev-vcs/git
gui-libs/eglexternalplatform
kde-frameworks/extra-cmake-modules
media-libs/glew
media-libs/gst-plugins-base:1.0
media-libs/gstreamer:1.0
media-plugins/gst-plugins-gtk:1.0
@@ -30,7 +31,6 @@ REQUIRED_DEV_PACKAGES=(
sys-devel/gettext
sys-devel/m4
virtual/libudev
virtual/opengl
x11-libs/gtk+:3
dev-util/pkgconf
dev-lang/yasm
+1 -1
View File
@@ -21,7 +21,7 @@ REQUIRED_DEV_PACKAGES=(
libspnav-devel
libtool
m4
Mesa-libGL-devel
glu-devel
ninja-build
openssl-devel
perl-FindBin-Real
+1
View File
@@ -189,6 +189,7 @@ else ()
target_link_libraries(OrcaSlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads pangoft2-1.0)
endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
if (SLIC3R_GUI)
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
target_link_libraries(OrcaSlicer libslic3r_gui 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
echo "Error: missing host OpenGL runtime library libOpenGL.so.0." >&2
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0" >&2
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0 and libglu1-mesa" >&2
echo "On Arch/CachyOS, install: libglvnd" >&2
exit 1
fi
-3
View File
@@ -411,9 +411,6 @@ void AppConfig::set_defaults()
if (get("show_overhang").empty())
set_bool("show_overhang", false);
if (get("show_center_of_mass").empty())
set_bool("show_center_of_mass", false);
#ifdef _WIN32
//#ifdef SUPPORT_3D_CONNEXION
-2
View File
@@ -112,8 +112,6 @@ set(lisbslic3r_sources
CommonDefs.hpp
Config.cpp
Config.hpp
ConnectedBodies.cpp
ConnectedBodies.hpp
ContourZ.cpp
CustomGCode.cpp
CustomGCode.hpp
+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
// associated csg operation. A collection of CSGPartT objects can then
// be interpreted as one model and used in various contexts. It can be assembled
// with CGAL or OpenVDB or provided to a ray-tracer to
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to
// deal with various parts of it according to the supported CSG types...
//
// A few simple templated interface functions are provided here and a default
-292
View File
@@ -1,292 +0,0 @@
#include "ConnectedBodies.hpp"
#include "AABBTreeIndirect.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
#include "Geometry/ConvexHull.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "TriangleMesh.hpp"
#include "TriangleMeshSlicer.hpp"
#include "libslic3r.h"
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <functional>
#include <limits>
#include <utility>
#include <vector>
namespace Slic3r {
std::vector<std::vector<size_t>> connected_bodies(const std::vector<const ExPolygons *> &layers, size_t &count,
const std::function<void()> &throw_if_canceled)
{
// Union-find over the islands of all layers, numbered layer after layer.
std::vector<size_t> first(layers.size() + 1, 0);
for (size_t l = 0; l < layers.size(); ++l)
first[l + 1] = first[l] + layers[l]->size();
std::vector<size_t> parent(first.back());
for (size_t i = 0; i < parent.size(); ++i)
parent[i] = i;
const auto find = [&parent](size_t i) {
while (parent[i] != i)
i = parent[i] = parent[parent[i]];
return i;
};
std::vector<std::vector<BoundingBox>> boxes(layers.size());
for (size_t l = 0; l < layers.size(); ++l)
for (const ExPolygon &island : *layers[l])
boxes[l].emplace_back(get_extents(island));
for (size_t l = 0; l + 1 < layers.size(); ++l) {
if (throw_if_canceled)
throw_if_canceled();
// Index the smaller of the two layers, so that a fragmented layer is not scanned island by island.
size_t a_layer = l;
size_t b_layer = l + 1;
if (layers[a_layer]->size() < layers[b_layer]->size())
std::swap(a_layer, b_layer);
if (layers[b_layer]->empty())
continue;
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> wrappers;
wrappers.reserve(boxes[b_layer].size());
for (size_t b = 0; b < boxes[b_layer].size(); ++b)
wrappers.emplace_back(b, boxes[b_layer][b]);
IslandTree tree;
tree.build_modify_input(wrappers);
for (size_t a = 0; a < boxes[a_layer].size(); ++a) {
const IslandTree::BoundingBox query(boxes[a_layer][a].min, boxes[a_layer][a].max);
AABBTreeIndirect::traverse(
tree, [&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
[&](const IslandTree::Node &node) {
// The tree's boxes are widened by an epsilon, and islands already joined need no clipping.
const size_t b = node.idx;
if (boxes[a_layer][a].overlap(boxes[b_layer][b]) && find(first[a_layer] + a) != find(first[b_layer] + b) &&
!intersection_ex((*layers[a_layer])[a], (*layers[b_layer])[b]).empty())
parent[find(first[a_layer] + a)] = find(first[b_layer] + b);
return true;
});
}
}
std::vector<size_t> body(parent.size(), std::numeric_limits<size_t>::max());
std::vector<std::vector<size_t>> out(layers.size());
count = 0;
for (size_t l = 0; l < layers.size(); ++l)
for (size_t i = 0; i < layers[l]->size(); ++i) {
size_t &b = body[find(first[l] + i)];
if (b == std::numeric_limits<size_t>::max())
b = count++;
out[l].emplace_back(b);
}
return out;
}
IslandLocator::IslandLocator(const ExPolygons &islands, coord_t margin) : m_islands(&islands), m_alone(islands.size(), true)
{
m_boxes.reserve(islands.size());
for (const ExPolygon &island : islands)
m_boxes.emplace_back(get_extents(island).inflated(margin));
// Sweep the boxes along x, so that only those reaching each other are compared.
std::vector<size_t> order(m_boxes.size());
for (size_t i = 0; i < order.size(); ++i)
order[i] = i;
std::sort(order.begin(), order.end(), [this](size_t l, size_t r) { return m_boxes[l].min.x() < m_boxes[r].min.x(); });
for (size_t a = 0; a < order.size(); ++a)
for (size_t b = a + 1; b < order.size() && m_boxes[order[b]].min.x() <= m_boxes[order[a]].max.x(); ++b)
if (m_boxes[order[a]].overlap(m_boxes[order[b]]))
m_alone[order[a]] = m_alone[order[b]] = false;
}
bool IslandLocator::holds(size_t island, const Point &point, bool strict) const
{
return m_boxes[island].contains(point) && ((m_alone[island] && !strict) || (*m_islands)[island].contains(point));
}
std::pair<int, double> IslandLocator::find(const Point &point, bool strict) const
{
int nearest = -1;
double distance = std::numeric_limits<double>::max();
for (size_t i = 0; i < m_boxes.size(); ++i)
if (m_boxes[i].contains(point)) {
if ((m_alone[i] && !strict) || (*m_islands)[i].contains(point))
return { int(i), 0. };
if (const double d = ((*m_islands)[i].point_projection(point) - point).cast<double>().squaredNorm(); d < distance) {
distance = d;
nearest = int(i);
}
}
return { nearest, distance };
}
// The area of polygons and their first and second moments of area, which holes, running clockwise, subtract.
struct AreaMoments
{
double area{ 0. };
Vec2d first{ Vec2d::Zero() };
// Of x^2, y^2 and xy.
Vec3d second{ Vec3d::Zero() };
void add(const Polygon &polygon)
{
if (polygon.points.size() < 3)
return;
Vec2d p1 = unscaled(polygon.points.back());
for (const Point &point : polygon.points) {
const Vec2d p2 = unscaled(point);
const double a = cross2(p1, p2);
area += a / 2.;
first += a / 6. * (p1 + p2);
second += a / 12. *
Vec3d(p1.x() * p1.x() + p1.x() * p2.x() + p2.x() * p2.x(), p1.y() * p1.y() + p1.y() * p2.y() + p2.y() * p2.y(),
p1.x() * p1.y() + p2.x() * p2.y() + 0.5 * (p1.x() * p2.y() + p2.x() * p1.y()));
p1 = p2;
}
}
};
// Mass, volume and the first and second moments of mass about the origin.
struct Moments
{
double mass{ 0. };
double volume{ 0. };
Vec3d first{ Vec3d::Zero() };
Matrix3d second{ Matrix3d::Zero() };
void add(const Moments &other)
{
mass += other.mass;
volume += other.volume;
first += other.first;
second += other.second;
}
};
BoundingBoxf3 SolidBody::bounding_box(const Transform3d &trafo) const
{
BoundingBoxf3 box;
for (const Point &point : hull.points)
for (const double z : { z_min, z_max })
box.merge(trafo * Vec3d(unscaled(point.x()), unscaled(point.y()), z));
return box;
}
std::vector<SolidBody> solid_bodies(const std::vector<MeshInPlace> &solids, const std::vector<double> &densities,
const std::vector<MeshInPlace> &negatives, size_t slabs)
{
assert(densities.size() == solids.size());
double z_min = std::numeric_limits<double>::max();
double z_max = std::numeric_limits<double>::lowest();
for (const auto &[mesh, trafo] : solids)
for (const stl_vertex &v : mesh->vertices) {
const double z = (trafo * v.cast<double>()).z();
z_min = std::min(z_min, z);
z_max = std::max(z_max, z);
}
if (z_min >= z_max || slabs == 0)
return {};
// Each slab sliced at its middle.
const double thickness = (z_max - z_min) / double(slabs);
std::vector<float> zs(slabs);
for (size_t k = 0; k < slabs; ++k)
zs[k] = float(z_min + (double(k) + 0.5) * thickness);
MeshSlicingParamsEx params;
const auto slice = [&zs, &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
@@ -1,61 +0,0 @@
#pragma once
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "TriangleMesh.hpp"
#include "libslic3r.h"
#include <admesh/stl.h>
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
namespace Slic3r {
// The connected body of each island of each layer: islands of adjacent layers whose slices overlap are one body.
// Bodies are numbered from 0 in the order of their first island.
std::vector<std::vector<size_t>> connected_bodies(const std::vector<const ExPolygons *> &layers, size_t &count,
const std::function<void()> &throw_if_canceled = nullptr);
// Finds the island of a layer that a point lies in, testing the polygons only where the boxes of several islands hold it.
class IslandLocator
{
public:
// The islands must outlive the locator. Their boxes are widened by the margin, for points reaching past an outline.
IslandLocator(const ExPolygons &islands, coord_t margin);
// Whether the island holds the point, or its box does where no other box reaches unless strict.
bool holds(size_t island, const Point &point, bool strict = false) const;
// The island holding the point as above, else the nearest one whose box holds it, with the squared distance to it; -1
// for none.
std::pair<int, double> find(const Point &point, bool strict = false) const;
const std::vector<BoundingBox> &boxes() const { return m_boxes; }
private:
const ExPolygons *m_islands;
std::vector<BoundingBox> m_boxes;
std::vector<bool> m_alone;
};
using MeshInPlace = std::pair<const indexed_triangle_set *, Transform3d>;
// A connected body of solids, with its outline seen from above as a convex hull and the height it spans.
struct SolidBody : MassProperties
{
Polygon hull;
double z_min{ 0. };
double z_max{ 0. };
// Its box once transformed, tight for a transformation that rotates about z only.
BoundingBoxf3 bounding_box(const Transform3d &trafo) const;
};
// Each connected body of the union of the solids less the negatives, sliced into slabs, each solid weighing its density.
// Where solids overlap, the later one counts, as slicing prints it.
std::vector<SolidBody> solid_bodies(const std::vector<MeshInPlace> &solids, const std::vector<double> &densities,
const std::vector<MeshInPlace> &negatives, size_t slabs);
} // namespace Slic3r
-152
View File
@@ -24,7 +24,6 @@
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "PrintBase.hpp"
#include "ConnectedBodies.hpp"
#include "PrintConfig.hpp"
#include "enum_bitmask.hpp"
#include "libslic3r.h"
@@ -2591,156 +2590,6 @@ WipeTowerType GCode::wipe_tower_type()
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)
{
PROFILE_CLEAR();
@@ -3263,7 +3112,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// modifies m_silent_time_estimator_enabled
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
print.get_layered_nozzle_group_result());
set_mass_locator(m_processor, print);
const bool is_bbl_printers = print.is_BBL_printer();
const bool skip_config_block = print.config().gcode_skip_config_block;
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
+1 -76
View File
@@ -89,6 +89,7 @@ static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f;
static const size_t MIN_EXTRUDERS_COUNT = 5;
static const float DEFAULT_FILAMENT_DIAMETER = 1.75f;
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 int DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE = 0;
static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
@@ -2603,10 +2604,6 @@ void GCodeProcessorResult::reset() {
lock();
moves.clear();
plate_mass = {};
object_masses.clear();
body_masses.clear();
support_masses.clear();
lines_ends.clear();
printable_area = Pointfs();
//BBS: add bed exclude area
@@ -3705,7 +3702,6 @@ void GCodeProcessor::reset()
m_g1_line_id = 0;
m_layer_id = 0;
m_cp_color.reset();
m_mass_locator = nullptr;
m_producer = EProducer::Unknown;
@@ -3845,7 +3841,6 @@ void GCodeProcessor::process_buffer(const std::string &buffer)
void GCodeProcessor::finalize(bool post_process)
{
m_result.z_offset = m_z_offset;
finalize_object_masses();
// update width/height of wipe moves
for (GCodeProcessorResult::MoveVertex& move : m_result.moves) {
@@ -5474,9 +5469,6 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[filament_id] - plate_offset);
}
if (type == EMoveType::Extrude)
add_object_mass(filament_id, area_filament_cross_section * delta_pos[E]);
// store move
store_move_vertex(type);
}
@@ -7285,73 +7277,6 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
}
}
void GCodeProcessorResult::ObjectMass::add(const Sum &sum, const BoundingBoxf3 &extent, size_t layer)
{
box.merge(extent);
if (printed_up_to_layer.size() <= layer)
printed_up_to_layer.resize(layer + 1);
printed_up_to_layer[layer].add(sum);
}
void GCodeProcessor::add_object_mass(int filament_id, float volume)
{
// Skirt, prime tower and custom G-code belong to no object.
const ExtrusionRole role = m_extrusion_role;
if (volume <= 0.f || role == erNone || role == erSkirt || role == erWipeTower || role == erCustom || role == erMixed)
return;
const bool has_density = size_t(filament_id) < m_result.filament_densities.size() && m_result.filament_densities[filament_id] > 0.f;
const double mass = double(volume) * (has_density ? m_result.filament_densities[filament_id] : DEFAULT_FILAMENT_DENSITY);
// In the frame of the stored moves, the bead's center half its height below the nozzle, from the move's start to its end.
const Vec3d half_height = 0.5 * double(m_height) * Vec3d::UnitZ();
const Vec3d offset = Vec3d(m_x_offset, m_y_offset, -m_z_offset) - half_height + m_extruder_offsets[filament_id].cast<double>();
const Vec3d start = Vec3d(m_start_position[X], m_start_position[Y], m_start_position[Z]) + offset;
const Vec3d end = Vec3d(m_end_position[X], m_end_position[Y], m_end_position[Z]) + offset;
// The second moments of a uniform segment.
const GCodeProcessorResult::ObjectMass::Sum sum{ mass, double(volume), 0.5 * mass * (start + end),
mass / 3. * (start.cwiseProduct(start) + start.cwiseProduct(end) + end.cwiseProduct(end)) };
// Of the bead's center line and its height, as its width is only estimated. Merged, as a wall along an axis is flat.
BoundingBoxf3 extent;
extent.merge(start.cwiseMin(end) - half_height);
extent.merge(start.cwiseMax(end) + half_height);
const bool part = role != erBrim && !is_support(role);
const size_t layer = std::max<unsigned int>(1, m_layer_id) - 1;
m_result.plate_mass.add(sum, extent, layer);
// The brim belongs to the plate alone.
if (role == erBrim || !m_mass_locator)
return;
const auto add = [&sum, &extent, layer](std::vector<GCodeProcessorResult::ObjectMass> &masses, int index) {
if (index < 0)
return;
if (masses.size() <= size_t(index))
masses.resize(index + 1);
masses[index].add(sum, extent, layer);
};
// At the nozzle's height, which the layers print at.
const MassLocation location = m_mass_locator(0.5 * (start + end) + half_height, !part);
if (part) {
add(m_result.object_masses, location.object);
add(m_result.body_masses, location.body);
} else
add(m_result.support_masses, location.object);
}
void GCodeProcessor::finalize_object_masses()
{
const auto accumulate = [](GCodeProcessorResult::ObjectMass &object) {
for (size_t i = 1; i < object.printed_up_to_layer.size(); ++i)
object.printed_up_to_layer[i].add(object.printed_up_to_layer[i - 1]);
};
accumulate(m_result.plate_mass);
for (GCodeProcessorResult::ObjectMass &object : m_result.object_masses)
accumulate(object);
for (GCodeProcessorResult::ObjectMass &body : m_result.body_masses)
accumulate(body);
for (GCodeProcessorResult::ObjectMass &support : m_result.support_masses)
accumulate(support);
}
void GCodeProcessor::set_extrusion_role(ExtrusionRole role)
{
m_used_filaments.process_role_cache(this);
-62
View File
@@ -3,7 +3,6 @@
#include "libslic3r/CommonDefs.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/ArcFitter.hpp"
@@ -36,9 +35,6 @@ namespace Slic3r {
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
#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"
@@ -274,44 +270,9 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
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;
unsigned int id;
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.
std::vector<size_t> lines_ends;
Pointfs printable_area;
@@ -399,10 +360,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
filename = std::forward<Other>(other).filename;
id = std::forward<Other>(other).id;
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;
printable_area = std::forward<Other>(other).printable_area;
bed_exclude_area = std::forward<Other>(other).bed_exclude_area;
@@ -1142,15 +1099,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
};
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// 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:
CommandProcessor m_command_processor;
GCodeReader m_parser;
@@ -1178,7 +1126,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
bool m_skippable{false};
SkipType m_skippable_type{SkipType::stNone};
int m_object_label_id{-1};
MassLocator m_mass_locator;
float m_print_z{0.0f};
std::vector<float> m_remaining_volume;
ExtruderTemps m_filament_nozzle_temp;
@@ -1333,13 +1280,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
const std::vector<std::set<int>>& unprintable_filament_types );
void apply_config(const PrintConfig& config);
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
// 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) {
@@ -1594,8 +1534,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
//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 add_object_mass(int filament_id, float volume);
void finalize_object_masses();
void set_extrusion_role(ExtrusionRole role);
// Resolve the SKIPPABLE_TYPE payload to a SkipType.
+62 -13
View File
@@ -12,7 +12,6 @@
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ConnectedBodies.hpp"
#include "Geometry.hpp"
#include "I18N.hpp"
#include "Layer.hpp"
@@ -748,19 +747,69 @@ void PrintObject::prepare_infill()
for (Layer *layer : m_layers)
layer->lslices_separated_component_ids.clear();
if (needs_separated_components) {
std::vector<const ExPolygons *> islands;
islands.reserve(m_layers.size());
for (const Layer *layer : m_layers)
islands.emplace_back(&layer->lslices);
size_t bodies = 0;
std::vector<std::vector<size_t>> ids = connected_bodies(islands, bodies, [this]() { m_print->throw_if_canceled(); });
// Orca: Merge the bounding boxes of the islands of each body.
m_separated_body_bboxes.assign(bodies, BoundingBox());
for (size_t i = 0; i < m_layers.size(); ++ i) {
const size_t nl = m_layers.size();
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
for (size_t i = 0; i < nl; ++ i)
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
const size_t nreg = offset[nl];
// Orca: Union-find over every (layer, island).
std::vector<size_t> parent(nreg);
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
auto find = [&parent](size_t x) {
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
return x;
};
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
// Orca: Index the smaller of two consecutive layers instead of scanning every
// pair of islands. The tree prunes distant boxes on fragmented models; exact
// polygon intersections still decide connectivity for the remaining candidates.
for (size_t i = 0; i + 1 < nl; ++ i) {
m_print->throw_if_canceled();
size_t layer_a = i, layer_b = i + 1;
if (m_layers[layer_a]->lslices.size() < m_layers[layer_b]->lslices.size())
std::swap(layer_a, layer_b);
const Layer *la = m_layers[layer_a], *lb = m_layers[layer_b];
if (lb->lslices.empty())
continue;
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(lb->lslices.size());
for (size_t b = 0; b < lb->lslices.size(); ++ b)
bboxes.emplace_back(b, lb->lslices_bboxes[b]);
IslandTree tree;
tree.build_modify_input(bboxes);
for (size_t a = 0; a < la->lslices.size(); ++ a) {
const IslandTree::BoundingBox query(la->lslices_bboxes[a].min, la->lslices_bboxes[a].max);
AABBTreeIndirect::traverse(tree,
[&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
[&](const IslandTree::Node &node) {
const size_t b = node.idx;
// Orca: Tree boxes include an epsilon, so retain the original box
// filter. Already-connected islands cannot change the partition
// and need no further polygon intersection.
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
find(offset[layer_a] + a) != find(offset[layer_b] + b) &&
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
unite(offset[layer_a] + a, offset[layer_b] + b);
return true;
});
}
}
// Orca: Number the bodies by their first island and merge the bounding boxes of their islands.
std::vector<size_t> body_of_root(nreg, size_t(-1));
for (size_t i = 0; i < nl; ++ i) {
Layer *layer = m_layers[i];
for (size_t a = 0; a < layer->lslices.size(); ++ a)
m_separated_body_bboxes[ids[i][a]].merge(layer->lslices_bboxes[a]);
layer->lslices_separated_component_ids = std::move(ids[i]);
layer->lslices_separated_component_ids.resize(layer->lslices.size());
for (size_t a = 0; a < layer->lslices.size(); ++ a) {
size_t &body = body_of_root[find(offset[i] + a)];
if (body == size_t(-1)) {
body = m_separated_body_bboxes.size();
m_separated_body_bboxes.emplace_back();
}
m_separated_body_bboxes[body].merge(layer->lslices_bboxes[a]);
layer->lslices_separated_component_ids[a] = body;
}
}
}
-34
View File
@@ -1495,40 +1495,6 @@ float its_volume(const indexed_triangle_set &its)
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)
{
if (its.indices.empty())
-15
View File
@@ -9,7 +9,6 @@
#include <array>
#include <cereal/specialize.hpp>
#include <functional>
#include <utility>
#include <vector>
#include "BoundingBox.hpp"
#include "Line.hpp"
@@ -325,20 +324,6 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
}
float its_volume(const indexed_triangle_set &its);
// 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);
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
-12
View File
@@ -1441,10 +1441,6 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
wxGetApp().plater()->schedule_background_process();
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.
// Belt printers: when the designed (upright) view is active, back-transform
@@ -1880,10 +1876,6 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
void GCodeViewer::load_as_preview(libvgcode::GCodeInputData&& data)
{
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);
for (auto& move_type_times : m_move_type_times)
@@ -1963,10 +1955,6 @@ void GCodeViewer::reset()
m_move_type_distances.fill(0.0f);
m_print_statistics.reset();
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_right_extruder_filament.clear();
m_sequential_view.gcode_window.reset();
-8
View File
@@ -260,10 +260,6 @@ private:
GCodeProcessorResult::SettingsIds m_settings_ids;
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 };
mutable bool m_no_render_path { false };
@@ -347,10 +343,6 @@ public:
std::vector<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
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_layers_count() const { return m_viewer.get_layers_count(); }
// ORCA: realistic view. Changes whenever the toolpaths casting shadows do.
-338
View File
@@ -85,9 +85,7 @@
#include "3DScene.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "CameraUtils.hpp"
#include "GLModel.hpp"
#include "GLShader.hpp"
#include "libslic3r/ConnectedBodies.hpp"
#include "GUI.hpp"
#include "Tab.hpp"
#include "GUI_Preview.hpp"
@@ -138,7 +136,6 @@
#include <tbb/spin_mutex.h>
#include <boost/functional/hash.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp>
@@ -1003,325 +1000,6 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
}
}
// The sums a solid adds to a marker.
static GCodeProcessorResult::ObjectMass::Sum mass_sum(const MassProperties& solid)
{
return { solid.mass, solid.volume, solid.mass * solid.center,
solid.mass * (solid.spread.diagonal() + solid.center.cwiseProduct(solid.center)) };
}
// On screen, of the plates, the objects, the supports and the bodies, each smaller than the one before, so that markers at
// one place still show.
static constexpr std::array<double, 4> marker_radii{ 9., 7., 6., 5. };
// As the canvas toolbar scales for the display's DPI.
static double marker_scale(const GLCanvas3D& canvas)
{
double scale = canvas.get_scale();
#ifdef WIN32
scale *= double(get_dpi_for_window(wxGetApp().GetTopWindow())) / double(DPI_DEFAULT);
#endif // WIN32
return scale;
}
GLCanvas3D::CenterOfMass::Markers GLCanvas3D::CenterOfMass::model_markers(const GLCanvas3D& canvas)
{
Markers markers;
if (canvas.get_model() == nullptr)
return markers;
struct Instance
{
Transform3d trafo;
std::vector<const GLVolume*> volumes;
};
std::map<int, std::map<int, Instance>> objects;
const ModelObjectPtrs& model_objects = canvas.get_model()->objects;
for (const GLVolume* volume : canvas.get_volumes().volumes) {
const int obj_idx = volume->object_idx();
const int vol_idx = volume->volume_idx();
if (!volume->is_active || volume->is_wipe_tower || obj_idx < 0 || obj_idx >= int(model_objects.size()) || vol_idx < 0 ||
vol_idx >= int(model_objects[obj_idx]->volumes.size()))
continue;
Instance& instance = objects[obj_idx][volume->instance_idx()];
instance.trafo = volume->get_instance_transformation().get_matrix();
instance.volumes.emplace_back(volume);
}
// From the filament presets, as the plater config holds the values of the last filament edited only.
const PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
std::vector<double> filament_densities;
for (const std::string& name : preset_bundle.filament_presets)
filament_densities.emplace_back(preset_bundle.filaments.find_preset(name, true)->config.opt_float("filament_density", 0));
const auto density = [&filament_densities](const ModelVolume& volume) {
const size_t filament = size_t(std::max(1, volume.extruder_id()));
const double density = filament <= filament_densities.size() ? filament_densities[filament - 1] : 0.;
return density > 0. ? density : double(DEFAULT_FILAMENT_DENSITY);
};
// One per plate, of the instances on it.
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
std::map<int, Marker> plates;
std::map<size_t, MassProperties> meshes;
std::map<size_t, Bodies> bodies;
for (const auto& [obj_idx, instances] : objects) {
const ModelObject& object = *model_objects[obj_idx];
// An assembly is sliced, so that its overlapping parts are united and its negative volumes cut away, in the
// order of its volumes, as the later one prints where two overlap.
std::vector<const GLVolume*> volumes = instances.begin()->second.volumes;
std::sort(volumes.begin(), volumes.end(), [](const GLVolume* l, const GLVolume* r) { return l->volume_idx() < r->volume_idx(); });
std::vector<MeshInPlace> solids;
std::vector<double> densities;
std::vector<MeshInPlace> negatives;
std::vector<Bodies::Volume> sliced;
for (const GLVolume* volume : volumes) {
const ModelVolume& model_volume = *object.volumes[volume->volume_idx()];
if (!model_volume.is_model_part() && !model_volume.is_negative_volume())
continue;
const Transform3d trafo = volume->get_volume_transformation().get_matrix();
if (model_volume.is_model_part()) {
solids.emplace_back(&model_volume.mesh().its, trafo);
densities.emplace_back(density(model_volume));
} else
negatives.emplace_back(&model_volume.mesh().its, trafo);
sliced.push_back({ model_volume.id().id, model_volume.is_negative_volume(), model_volume.is_model_part() ? densities.back() : 0., trafo });
}
const std::vector<SolidBody>* assembly = nullptr;
if (solids.size() > 1 || (!solids.empty() && !negatives.empty())) {
// Coarser while a part is dragged.
const size_t slabs = canvas.is_dragging() ? 100 : 500;
const auto cached = m_bodies.find(object.id().id);
const bool valid = cached != m_bodies.end() && cached->second.slabs >= slabs && cached->second.volumes == sliced;
Bodies& entry = bodies[object.id().id];
entry = valid ? std::move(cached->second) : Bodies{ std::move(sliced), slabs, solid_bodies(solids, densities, negatives, slabs) };
assembly = &entry.bodies;
}
for (const auto& [inst_idx, instance] : instances) {
// The box of its parts, which the object's size shows.
Marker object_marker;
object_marker.assembly = assembly != nullptr;
for (const GLVolume* volume : instance.volumes)
if (object.volumes[volume->volume_idx()]->is_model_part())
object_marker.box.merge(volume->transformed_convex_hull_bounding_box());
if (assembly != nullptr) {
std::vector<Marker> parts;
for (const SolidBody& body : *assembly)
if (body.mass > 0.) {
parts.push_back({ mass_sum(body.transformed(instance.trafo)), body.bounding_box(instance.trafo) });
object_marker.sum.add(parts.back().sum);
}
if (parts.size() > 1)
append(markers[mkBody], std::move(parts));
} else
for (const GLVolume* volume : instance.volumes) {
// The parts the object info's volume sums.
const ModelVolume& model_volume = *object.volumes[volume->volume_idx()];
if (!model_volume.is_model_part())
continue;
const auto [it, inserted] = meshes.try_emplace(model_volume.id().id);
if (inserted) {
const auto cached = m_meshes.find(it->first);
it->second = cached != m_meshes.end() ? cached->second : its_mass_properties(model_volume.mesh().its);
}
MassProperties part = it->second.transformed(volume->world_matrix());
part.mass *= density(model_volume);
object_marker.sum.add(mass_sum(part));
}
if (object_marker.sum.mass > 0.) {
if (const int plate = plate_list.find_instance(obj_idx, inst_idx); plate >= 0) {
plates[plate].sum.add(object_marker.sum);
plates[plate].box.merge(object_marker.box);
}
markers[mkObject].emplace_back(std::move(object_marker));
}
}
}
m_meshes = std::move(meshes);
m_bodies = std::move(bodies);
for (auto& [plate, marker] : plates)
markers[mkPlate].emplace_back(std::move(marker));
return markers;
}
void GLCanvas3D::CenterOfMass::render(GLCanvas3D& canvas)
{
m_drawn = {};
const bool preview = canvas.m_canvas_type == ECanvasType::CanvasPreview;
// The other gizmos work on the surface the marker would cover.
const GLGizmosManager::EType gizmo = canvas.get_gizmos_manager().get_current_type();
if (!wxGetApp().show_center_of_mass() || canvas.m_design_canvas ||
!(canvas.m_canvas_type == ECanvasType::CanvasView3D || (preview && canvas.m_render_preview)) ||
(gizmo != GLGizmosManager::Undefined && gizmo != GLGizmosManager::Move && gizmo != GLGizmosManager::Rotate &&
gizmo != GLGizmosManager::Scale && gizmo != GLGizmosManager::Flatten))
return;
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
// Preview adds markers for what is printed up to the top layer shown.
if (preview) {
const GCodeViewer& gcode_viewer = canvas.get_gcode_viewer();
m_top_layer = gcode_viewer.get_layers_z_range()[1];
const auto add = [this](const GCodeProcessorResult::ObjectMass& mass, MarkerKind kind) {
if (const Sum total = mass.total(); total.mass > 0.)
m_drawn[0][kind].push_back({ total, mass.box, mass.assembly });
if (!mass.printed_up_to_layer.empty())
if (const Sum& sum = mass.printed_up_to_layer[std::min(m_top_layer, mass.printed_up_to_layer.size() - 1)]; sum.mass > 0.)
m_drawn[1][kind].push_back({ sum, mass.box, mass.assembly });
};
add(gcode_viewer.get_plate_mass(), mkPlate);
for (const GCodeProcessorResult::ObjectMass& object : gcode_viewer.get_object_masses())
add(object, mkObject);
for (const GCodeProcessorResult::ObjectMass& body : gcode_viewer.get_body_masses())
add(body, mkBody);
for (const GCodeProcessorResult::ObjectMass& support : gcode_viewer.get_support_masses())
add(support, mkSupport);
} else
m_drawn[0] = model_markers(canvas);
if (std::all_of(m_drawn.begin(), m_drawn.end(),
[](const Markers& markers) { return std::all_of(markers.begin(), markers.end(), [](const auto& kind) { return kind.empty(); }); }))
return;
if (!m_octants[0].is_initialized()) {
// A resolution divisible by 4 puts every triangle within one octant.
const GLModel::Geometry sphere = smooth_sphere(32, 1.f);
std::array<GLModel::Geometry, 2> octants;
for (size_t i = 0; i + 2 < sphere.indices_count(); i += 3) {
const std::array<unsigned int, 3> ids = { sphere.extract_index(i), sphere.extract_index(i + 1), sphere.extract_index(i + 2) };
const Vec3f c = sphere.extract_position_3(ids[0]) + sphere.extract_position_3(ids[1]) + sphere.extract_position_3(ids[2]);
GLModel::Geometry& octant = octants[c.x() * c.y() * c.z() > 0.f ? 0 : 1];
for (const unsigned int id : ids)
octant.add_vertex(sphere.extract_position_3(id), sphere.extract_normal_3(id));
const auto n = (unsigned int)octant.vertices_count();
octant.add_triangle(n - 3, n - 2, n - 1);
}
for (size_t i = 0; i < octants.size(); ++i)
m_octants[i].init_from(std::move(octants[i]));
}
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
const double scale = marker_scale(canvas) * camera.get_inv_zoom();
// Seen through the object it lies in; culling keeps the sphere's far half behind its near one.
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_CULL_FACE));
shader->start_using();
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("view_normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3));
shader->set_uniform("emission_factor", 0.1f);
const std::array<std::array<ColorRGBA, 2>, mkCount> colors = { {
{ ColorRGBA(0.1f, 0.1f, 0.1f, 1.f), ColorRGBA::WHITE() },
{ ColorRGBA(0x5A / 255.f, 0x9B / 255.f, 0xD4 / 255.f, 1.f), ColorRGBA::WHITE() },
{ ColorRGBA(0.f, 0.6f, 0.f, 1.f), ColorRGBA(0.1f, 0.1f, 0.1f, 1.f) },
{ ColorRGBA(0.7f, 0.f, 0.f, 1.f), ColorRGBA::YELLOW() },
} };
const auto draw = [&](const Markers& markers, float alpha) {
for (size_t kind = 0; kind < mkCount; ++kind)
for (const Marker& marker : markers[kind]) {
shader->set_uniform("view_model_matrix", view_matrix * Geometry::translation_transform(marker.center()) *
Geometry::scale_transform(marker_radii[kind] * scale));
for (size_t i = 0; i < m_octants.size(); ++i) {
ColorRGBA color = colors[kind][i];
color.a(alpha);
m_octants[i].set_color(color);
m_octants[i].render();
}
}
};
// Preview fades the finished parts' markers under those of what is printed so far.
draw(m_drawn[0], preview ? 0.4f : 1.f);
draw(m_drawn[1], 1.f);
shader->stop_using();
glsafe(::glEnable(GL_DEPTH_TEST));
}
bool GLCanvas3D::CenterOfMass::on_left_down(GLCanvas3D& canvas, const Vec2d& mouse)
{
const bool shown = m_picked.has_value();
const Camera& camera = wxGetApp().plater()->get_camera();
const double scale = marker_scale(canvas) * camera.get_inv_zoom();
m_picked.reset();
// In the order they cover each other: what is printed so far over the finished print, smaller kinds over larger ones.
for (size_t set = m_drawn.size(); set-- > 0 && !m_picked;)
for (size_t kind = mkCount; kind-- > 0 && !m_picked;)
for (size_t index = 0; index < m_drawn[set][kind].size(); ++index) {
const Vec3d center = m_drawn[set][kind][index].center();
const std::vector<Vec3d> ends = { center, center + marker_radii[kind] * scale * camera.get_dir_right() };
const Points screen = CameraUtils::project(camera, ends);
if ((screen[0].cast<double>() - mouse).norm() <= (screen[1] - screen[0]).cast<double>().norm()) {
m_picked = Pick{ set, kind, index, m_drawn[set][kind].size() };
break;
}
}
if (shown || m_picked)
canvas._set_overlay_as_dirty();
return m_picked.has_value();
}
void GLCanvas3D::CenterOfMass::render_details(GLCanvas3D& canvas)
{
if (!m_picked)
return;
const Pick& pick = *m_picked;
const std::vector<Marker>& markers = m_drawn[pick.set][pick.kind];
// Gone with the markers, or with what it stood for.
if (markers.size() != pick.count) {
m_picked.reset();
return;
}
const Marker& marker = markers[pick.index];
const Sum& sum = marker.sum;
const Vec3d center = marker.center();
// About the axes through the center, from how far the mass spreads along the two others.
const Vec3d spread = (sum.second / sum.mass - center.cwiseProduct(center)).cwiseMax(0.);
const Vec3d inertia = sum.mass * Vec3d(spread.y() + spread.z(), spread.x() + spread.z(), spread.x() + spread.y());
// Beside the marker.
const Point screen = CameraUtils::project(wxGetApp().plater()->get_camera(), center);
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.set_next_window_pos(float(screen.x() + 2. * marker_radii[pick.kind] * marker_scale(canvas)), float(screen.y()), ImGuiCond_Always, 0.f, 0.5f);
const std::string title = pick.kind == mkPlate ? _u8L("Plate center of mass") :
pick.kind == mkBody ? _u8L("Part center of mass") :
pick.kind == mkSupport ? _u8L("Support center of mass") :
marker.assembly ? _u8L("Assembly center of mass") :
_u8L("Object center of mass");
bool open = true;
imgui.begin(title + "###center_of_mass", &open,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
if (ImGui::IsWindowAppearing())
imgui.set_requires_extra_frame();
if (canvas.get_canvas_type() == ECanvasType::CanvasPreview)
imgui.text(pick.set == 0 ? _u8L("Finished print") : (boost::format(_u8L("Printed up to layer %1%")) % (m_top_layer + 1)).str());
// Masses are in mg, volumes in mm³.
const auto xyz = [](const Vec3d& v, const char* format, const std::string& unit) {
return (boost::format(format) % v.x() % v.y() % v.z()).str() + " " + unit;
};
if (ImGui::BeginTable("##center_of_mass_details", 2)) {
const auto row = [](const std::string& label, const std::string& value) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORCA, label);
ImGui::TableSetColumnIndex(1);
ImGuiWrapper::text(value);
};
row(_u8L("Weight"), (boost::format("%.2f g") % (sum.mass / 1000.)).str());
row(_u8L("Volume"), (boost::format(u8"%.2f cm³") % (sum.volume / 1000.)).str());
if (marker.box.defined) {
row(_u8L("Center in bounding box"), xyz(center - marker.box.min, "X: %.2f, Y: %.2f, Z: %.2f", _u8L("mm")));
row(_u8L("Bounding box size"), xyz(marker.box.size(), "X: %.2f, Y: %.2f, Z: %.2f", _u8L("mm")));
}
row(_u8L("Moment of inertia"), xyz(inertia / 1000., "X: %.0f, Y: %.0f, Z: %.0f", u8"g·mm²"));
ImGui::EndTable();
}
imgui.end();
if (!open) {
m_picked.reset();
canvas._set_overlay_as_dirty();
}
}
void GLCanvas3D::Tooltip::set_text(const std::string& text)
{
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
@@ -2842,9 +2520,6 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
m_frame_profiler.mark("ssao");
}
// After the occlusion pass, which would shade it as the surface behind it.
m_center_of_mass.render(*this);
if (_is_fxaa_enabled()) {
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
m_frame_profiler.mark("fxaa");
@@ -4838,12 +4513,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
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;
std::map<MouseButton, MouseAction> button_mappings;
@@ -9694,7 +9363,6 @@ void GLCanvas3D::_render_overlays()
}*/
}
m_labels.render(sorted_instances);
m_center_of_mass.render_details(*this);
_render_3d_navigator();
@@ -10682,12 +10350,6 @@ void GLCanvas3D::_render_canvas_toolbar()
[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
// 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
-68
View File
@@ -2,9 +2,6 @@
#define slic3r_GLCanvas3D_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/ConnectedBodies.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include <cstdlib>
#include <imgui.h>
@@ -33,7 +30,6 @@
#include "Gizmos/GLGizmosManager.hpp"
#include "GUI_ObjectLayers.hpp"
#include "GLSelectionRectangle.hpp"
#include "GLModel.hpp"
#include "MeshUtils.hpp"
#include "GCodeViewer.hpp"
#include "Camera.hpp"
@@ -481,69 +477,6 @@ class GLCanvas3D
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
{
std::string m_text;
@@ -800,7 +733,6 @@ private:
int m_selected_extruder;
Labels m_labels;
CenterOfMass m_center_of_mass;
Tooltip m_tooltip;
bool m_tooltip_enabled{ true };
Slope m_slope;
+2 -3
View File
@@ -396,6 +396,8 @@ public:
Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
HMSQuery* get_hms_query() { return hms_query; }
NetworkAgent* getAgent() { return m_agent; }
// Version that wrote the app config before this run; empty when there was no app config.
const boost::optional<Semver>& last_config_version() const { return m_last_config_version; }
// Reconcile the live printer agent with the stored preset selection.
void switch_printer_agent();
@@ -435,9 +437,6 @@ public:
bool show_outline() const { return app_config->get_bool("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;};
std::vector<std::string> split_str(std::string src, std::string separator);
+3
View File
@@ -47,6 +47,9 @@ public:
const std::string& get_vendor() const;
const std::string& get_renderer() const;
// False until a GL context has been made current and queried; the getters below detect
// on first use, which needs a current context.
bool is_detected() const { return m_detected; }
bool is_core_profile() const { return m_core_profile; }
bool is_mesa() const;
+51 -14
View File
@@ -19318,6 +19318,38 @@ void Plater::send_gcode_finish(wxString name)
auto out_str = GUI::format(_L("The file %s has been sent to the printer's storage space and can be viewed on the printer."), name);
p->notification_manager->push_exporting_finished_notification(out_str, "", false);
}
namespace {
// export_3mf() assigns archive paths to previously unsaved SVGs. Puts them back, so an export that
// is not a project save does not change what a later project save writes.
class SvgArchivePathsRestorer
{
public:
explicit SvgArchivePathsRestorer(Model& model)
{
for (ModelObject* object : model.objects)
for (ModelVolume* volume : object->volumes)
if (volume != nullptr && volume->emboss_shape.has_value() && volume->emboss_shape->svg_file.has_value()) {
std::string* path_in_3mf = &volume->emboss_shape->svg_file->path_in_3mf;
m_paths.emplace_back(path_in_3mf, *path_in_3mf);
}
}
~SvgArchivePathsRestorer() { restore(); }
SvgArchivePathsRestorer(const SvgArchivePathsRestorer&) = delete;
SvgArchivePathsRestorer& operator=(const SvgArchivePathsRestorer&) = delete;
void restore()
{
for (const auto& [path_in_3mf, previous_path] : m_paths)
*path_in_3mf = previous_path;
}
private:
std::vector<std::pair<std::string*, std::string>> m_paths;
};
} // namespace
void Plater::export_core_3mf()
{
wxString path = p->get_export_file(FT_3MF);
@@ -19326,6 +19358,23 @@ void Plater::export_core_3mf()
export_3mf(path_u8, SaveStrategy::Silence);
}
bool Plater::export_3mf_copy(const boost::filesystem::path& output_path)
{
Model& model = p->model;
SvgArchivePathsRestorer svg_paths(model);
// With no design info, export_3mf() writes the signed-in account's user id as the designer; an
// empty one keeps it out. export_3mf() also drops design info without a designer name afterwards.
const std::shared_ptr<ModelDesignInfo> design_info = model.design_info;
if (design_info == nullptr)
model.design_info = std::make_shared<ModelDesignInfo>();
ScopeGuard restore_design_info([&model, design_info]() { model.design_info = design_info; });
// The project save's layout, plus Silence so the project file name stays as it is. Unlike a save
// it never adds FullPathSources: a copy is for sharing, and those are paths on this machine.
return export_3mf(output_path, SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence) == 0;
}
// Export the current project as a "published" 3MF: a pure export that never touches the
// project's file name, dirty state, backup path or title, and attaches the published metadata
// to the model only for the duration of the export (a later Save Project is a normal 3MF).
@@ -19375,19 +19424,10 @@ int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
std::string();
const std::string prev_payload = had_payload ? model.model_info->metadata_items.at(ORCA_PUBLISHED_CONFIG_TAG) : std::string();
// export_3mf() assigns archive paths to previously unsaved SVGs. Preserve those fields too,
// otherwise a publish changes what a later normal project save writes.
std::vector<std::pair<std::string*, std::string>> previous_svg_paths;
for (ModelObject* object : model.objects)
for (ModelVolume* volume : object->volumes)
if (volume != nullptr && volume->emboss_shape.has_value() && volume->emboss_shape->svg_file.has_value()) {
std::string* path_in_3mf = &volume->emboss_shape->svg_file->path_in_3mf;
previous_svg_paths.emplace_back(path_in_3mf, *path_in_3mf);
}
SvgArchivePathsRestorer svg_paths(model);
auto restore_temporary_state = [&]() {
for (const auto& [path_in_3mf, previous_path] : previous_svg_paths)
*path_in_3mf = previous_path;
svg_paths.restore();
if (!had_model_info) {
model.model_info = nullptr;
@@ -21230,9 +21270,6 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") {
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)
+3
View File
@@ -565,6 +565,9 @@ public:
void export_gcode_3mf(bool export_all = false);
void send_gcode_finish(wxString name);
void export_core_3mf();
// Write the project to output_path as a copy: the project's file name, dirty state and model stay
// as they are, and the signed-in account is not added as the designer. False when the write fails.
bool export_3mf_copy(const boost::filesystem::path& output_path);
// Export a "published" 3MF embedding the author-selected settings in the file metadata; a
// pure export that leaves the in-memory project untouched.
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
+127 -1
View File
@@ -1,16 +1,36 @@
#include "PluginHostBindings.hpp"
#include <pybind11/cast.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <libslic3r/Model.hpp>
#include <libslic3r/PresetBundle.hpp>
#include <libslic3r/AppConfig.hpp>
#include <libslic3r/Semver.hpp>
#include <libslic3r_version.h>
#include <slic3r/GUI/BuildCommit.hpp>
#include <slic3r/GUI/DeviceCore/DevManager.h>
#include <slic3r/GUI/DeviceManager.hpp>
#include <slic3r/GUI/GUI.hpp>
#include <slic3r/GUI/GUI_App.hpp>
#include <slic3r/GUI/OpenGLManager.hpp>
#include <slic3r/GUI/Plater.hpp>
#include <slic3r/Utils/CloudProvider.hpp>
#include <slic3r/Utils/NetworkAgent.hpp>
#include <slic3r/Utils/NetworkAgentFactory.hpp>
#include <slic3r/Utils/bambu_networking.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/optional/optional.hpp>
#include <memory>
#include <stdexcept>
#include <wx/app.h>
#include <wx/string.h>
#include <wx/thread.h>
#include <string>
#include <utility>
namespace py = pybind11;
@@ -41,6 +61,94 @@ PresetBundle* current_preset_bundle()
return preset_bundle;
}
GUI::GUI_App& current_app()
{
if (wxTheApp == nullptr)
throw std::runtime_error("OrcaSlicer application is not initialized");
return GUI::wxGetApp();
}
// The plater and the device list are only safe to touch from the UI thread, and the 3MF export
// renders thumbnails, which needs the UI thread's GL context.
void require_main_thread(const char* function_name)
{
if (!wxIsMainThread())
throw std::runtime_error(std::string(function_name) + "() must be called from the UI thread");
}
void export_3mf_copy(GUI::Plater& plater, const std::string& path)
{
require_main_thread("export_3mf_copy");
if (!boost::iends_with(path, ".3mf"))
throw py::value_error("export_3mf_copy() needs a path ending in .3mf");
// The thumbnails are rendered by the 3D view, which may not exist yet while plugins load.
if (!GUI::OpenGLManager::get_gl_info().is_detected())
throw std::runtime_error("export_3mf_copy() needs the 3D view, which is not ready yet");
// The file is written from C++, so raise the audit event Python's open(path, "w") raises: the
// plugin gets the same deny list, permissions and prompt as for writing the file itself.
if (PySys_Audit("open", "ssi", path.c_str(), "w", 0) < 0)
throw py::error_already_set();
if (!plater.export_3mf_copy(GUI::into_path(GUI::from_u8(path))))
throw std::runtime_error("Failed to write " + path);
}
py::object gl_info()
{
const GUI::OpenGLManager::GLInfo& info = GUI::OpenGLManager::get_gl_info();
if (!info.is_detected())
return py::none();
py::dict out;
out["vendor"] = info.get_vendor();
out["renderer"] = info.get_renderer();
out["version"] = info.get_version();
out["glsl_version"] = info.get_glsl_version();
out["core_profile"] = info.is_core_profile();
return std::move(out);
}
py::dict app_info()
{
GUI::GUI_App& app = current_app();
// The app config has no lock of its own; the UI thread is where it is written.
require_main_thread("app_info");
py::dict out;
out["version"] = SoftFever_VERSION;
out["build"] = GUI::build_commit_label;
out["mode"] = app.is_editor() ? "editor" : "gcode viewer";
out["language"] = GUI::into_u8(app.current_language_code_safe());
// The app config file is deny-listed for plugins; these are the parts a bug report needs.
const boost::optional<Semver>& config_version = app.last_config_version();
out["app_config_version"] = config_version && config_version->valid() ? config_version->to_string_sf() : std::string();
out["stealth_mode"] = app.app_config != nullptr && app.app_config->get_stealth_mode();
out["is_signed_in"] = app.is_user_login(ORCA_CLOUD_PROVIDER);
out["is_bambu_signed_in"] = app.is_user_login(BBL_CLOUD_PROVIDER);
out["network_plugin_version"] = NetworkAgent::is_network_module_loaded() ? NetworkAgent::get_version() : std::string();
const NetworkLibraryLoadError load_error = NetworkAgent::get_load_error();
out["network_plugin_error"] = load_error.has_error ? load_error.message : std::string();
return out;
}
py::object selected_printer()
{
GUI::GUI_App& app = current_app();
require_main_thread("selected_printer");
DeviceManager* devices = app.getDeviceManager();
MachineObject* machine = devices != nullptr ? devices->get_selected_machine() : nullptr;
if (machine == nullptr)
return py::none();
// Agent, model, link and firmware only: no name, serial, address or access code. Other agents
// fill the model and firmware with placeholders, so those come from Bambu printers only.
const bool bambu = machine->printer_agent_id == BBL_PRINTER_AGENT_ID;
py::dict out;
out["agent"] = machine->printer_agent_id;
out["model_id"] = bambu ? machine->printer_type : std::string();
out["connection"] = machine->connection_type();
out["online"] = machine->is_online();
out["connected"] = machine->is_connected();
out["firmware"] = bambu ? machine->get_ota_version() : std::string();
return std::move(out);
}
} // namespace
// Access to the live GUI application: the Plater and the module-level
@@ -52,7 +160,15 @@ void host_bindings::register_app(py::module_& host)
.def("model", static_cast<Model& (GUI::Plater::*)()>(&GUI::Plater::model), py::return_value_policy::reference_internal)
.def("is_project_dirty", &GUI::Plater::is_project_dirty)
.def("is_presets_dirty", &GUI::Plater::is_presets_dirty)
.def("inside_snapshot_capture", &GUI::Plater::inside_snapshot_capture);
.def("inside_snapshot_capture", &GUI::Plater::inside_snapshot_capture)
// Path the project was last opened from or saved to; empty while it has never been saved.
.def("project_path", [](GUI::Plater& plater) {
require_main_thread("project_path");
return GUI::into_u8(plater.get_project_filename(".3mf"));
})
.def("export_3mf_copy", &export_3mf_copy, py::arg("path"),
"Write the current project, unsaved changes included, to path as a 3MF copy. The project's "
"file name and saved state are not changed, and the signed-in account is not written as the designer.");
host.def("plater", &current_plater, py::return_value_policy::reference);
host.def("model", []() -> Model& {
@@ -68,6 +184,16 @@ void host_bindings::register_app(py::module_& host)
throw std::runtime_error("OrcaSlicer application is not initialized");
return GUI::into_u8(GUI::wxGetApp().current_language_code_safe());
});
host.def("app_info", &app_info,
"The running app: version, build, mode, language, app_config_version (the version that wrote the app config "
"before this run, empty when there was none), stealth_mode, is_signed_in (Orca Cloud), is_bambu_signed_in (Bambu Cloud), network_plugin_version (empty "
"when the Bambu network plugin is not loaded) and network_plugin_error (empty unless it failed to load).");
host.def("gl_info", &gl_info,
"OpenGL vendor, renderer, version, glsl_version and core_profile of the 3D view; None before it has been created.");
host.def("selected_printer", &selected_printer,
"Agent, model_id, connection ('lan' or 'cloud'), online, connected and firmware of the printer selected in the "
"device list; model_id and firmware are empty for non-Bambu agents. None when none is selected. Print hosts "
"configured in the printer preset (OctoPrint and the like) are not in the device list.");
}
} // namespace Slic3r
+12 -233
View File
@@ -6,12 +6,10 @@
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Utils.hpp"
#include "test_helpers.hpp"
@@ -20,7 +18,6 @@
#include <algorithm>
#include <cstddef>
#include <fstream>
#include <initializer_list>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Point.hpp"
#include <sstream>
@@ -102,22 +99,6 @@ TEST_CASE("Reserved keyword detection reports every offending line", "[GCodeProc
namespace {
void process_gcode(const std::string &gcode, GCodeProcessorResult &result)
{
FullPrintConfig config;
config.gcode_flavor.value = gcfMarlinFirmware;
// s_IsBBLPrinter selects the "; FEATURE: " role tags the G-code uses.
const bool was_bbl_printer = GCodeProcessor::s_IsBBLPrinter;
const ScopeGuard restore_bbl_printer([was_bbl_printer] { GCodeProcessor::s_IsBBLPrinter = was_bbl_printer; });
GCodeProcessor::s_IsBBLPrinter = true;
ScopedTemporaryFile temp(".gcode");
std::ofstream(temp.string()) << gcode;
GCodeProcessor processor;
processor.apply_config(config);
processor.process_file(temp.string());
result = std::move(processor.extract_result());
}
// Closed outer-wall squares, each after a fast travel and before an inner-wall move, so the processor
// records seams and inserts actual speed moves. virtual_moves adds a VG1 move after each square.
void process_squares(int squares, GCodeProcessorResult &result, bool virtual_moves = false)
@@ -133,42 +114,18 @@ void process_squares(int squares, GCodeProcessorResult &result, bool virtual_mov
if (virtual_moves)
gcode << "VG1 X20 Y30 F12000\n";
}
process_gcode(gcode.str(), result);
}
// Objects A and B on the first layer and A again on the second, with A's brim and support. The skirt and the
// prime tower belong to neither.
void process_two_objects(GCodeProcessorResult &result)
{
std::ostringstream gcode;
gcode << "M83\nG90\n"
<< "; CHANGE_LAYER\n; LAYER_HEIGHT: 0.2\nG1 Z0.2 F12000\n"
<< "; FEATURE: Skirt\nG1 X0 Y100 E5 F3000\n"
<< "; FEATURE: Brim\nG1 X8 Y8 F12000\nG1 X12 Y8 E1 F3000\n"
<< "; FEATURE: Support\nG1 X10 Y20 F12000\nG1 X10 Y30 E1 F3000\n"
<< "; FEATURE: Outer wall\nG1 X10 Y10 F12000\nG1 X20 Y10 E1 F3000\n"
<< "; FEATURE: Outer wall\nG1 X50 Y50 F12000\nG1 X60 Y50 E2 F3000\n"
<< "; FEATURE: Prime tower\nG1 X80 Y80 F12000\nG1 X90 Y80 E1 F3000\n"
<< "; CHANGE_LAYER\n; LAYER_HEIGHT: 0.2\nG1 Z0.4 F12000\n"
<< "; FEATURE: Outer wall\nG1 X10 Y10 F12000\nG1 X20 Y10 E1 F3000\n";
process_gcode(gcode.str(), result);
}
// Bead centers of process_two_objects(), half the 0.2 mm layer below the nozzle.
const Vec3d a_brim(10., 8., 0.1), a_support(10., 25., 0.1), a_wall_0(15., 10., 0.1), a_wall_1(15., 10., 0.3), b_wall(55., 50., 0.1);
Vec3d center_of(const GCodeProcessorResult::ObjectMass::Sum &sum) { return sum.moment / sum.mass; }
// One filament, so each bead weighs as much as the E it was extruded with.
Vec3d weighted_center(std::initializer_list<std::pair<double, Vec3d>> beads)
{
double mass = 0.;
Vec3d moment = Vec3d::Zero();
for (const auto &[e, center] : beads) {
mass += e;
moment += e * center;
}
return moment / mass;
FullPrintConfig config;
config.gcode_flavor.value = gcfMarlinFirmware;
// s_IsBBLPrinter selects the "; FEATURE: " role tags this G-code uses.
const bool was_bbl_printer = GCodeProcessor::s_IsBBLPrinter;
const ScopeGuard restore_bbl_printer([was_bbl_printer] { GCodeProcessor::s_IsBBLPrinter = was_bbl_printer; });
GCodeProcessor::s_IsBBLPrinter = true;
ScopedTemporaryFile temp(".gcode");
std::ofstream(temp.string()) << gcode.str();
GCodeProcessor processor;
processor.apply_config(config);
processor.process_file(temp.string());
result = std::move(processor.extract_result());
}
bool is_block_move(const GCodeProcessorResult::MoveVertex &move)
@@ -324,181 +281,3 @@ TEST_CASE("Rewritten G-code that cannot be re-read keeps the moves and hides the
REQUIRE(result.moves.size() == exported_moves.size());
CHECK(result.moves.back().gcode_id == exported_moves.back().gcode_id);
}
TEST_CASE("The plate's center of mass takes every extrusion of G-code without a print behind it", "[GCodeProcessor]")
{
GCodeProcessorResult result;
process_two_objects(result);
CHECK(result.object_masses.empty());
CHECK(result.body_masses.empty());
const GCodeProcessorResult::ObjectMass &plate = result.plate_mass;
REQUIRE(plate.printed_up_to_layer.size() == 2);
CHECK_THAT((center_of(plate.printed_up_to_layer.front()) -
weighted_center({ { 1., a_brim }, { 1., a_support }, { 1., a_wall_0 }, { 2., b_wall } })).norm(),
Catch::Matchers::WithinAbs(0., 1e-5));
CHECK_THAT((center_of(plate.printed_up_to_layer.back()) -
weighted_center({ { 1., a_brim }, { 1., a_support }, { 1., a_wall_0 }, { 2., b_wall }, { 1., a_wall_1 } })).norm(),
Catch::Matchers::WithinAbs(0., 1e-5));
// Each bead weighs its volume at the default density and spreads along its move, (a^2 + ab + b^2) / 3 for one from
// a to b: the brim from x 8 to 12 at y 8, the support at x 10 from y 20 to 30, A's walls from x 10 to 20 at y 10 and
// B's from x 50 to 60 at y 50 with twice the filament, all at z 0.1 but A's second wall at 0.3.
const GCodeProcessorResult::ObjectMass::Sum total = plate.total();
CHECK_THAT(total.mass / total.volume, Catch::Matchers::WithinRel(double(DEFAULT_FILAMENT_DENSITY), 1e-6));
const Vec3d second = total.second / total.mass;
CHECK_THAT(second.x(), Catch::Matchers::WithinRel((304. / 3. + 100. + 2. * 700. / 3. + 2. * 9100. / 3.) / 6., 1e-6));
CHECK_THAT(second.y(), Catch::Matchers::WithinRel((64. + 1900. / 3. + 2. * 100. + 2. * 2500.) / 6., 1e-6));
CHECK_THAT(second.z(), Catch::Matchers::WithinRel((5. * 0.01 + 0.09) / 6., 1e-5));
// The beads' center lines, brim and support included, from the first layer's bottom to the second's top.
CHECK_THAT((plate.box.min - Vec3d(8., 8., 0.)).norm(), Catch::Matchers::WithinAbs(0., 1e-5));
CHECK_THAT((plate.box.max - Vec3d(60., 50., 0.4)).norm(), Catch::Matchers::WithinAbs(0., 1e-5));
}
TEST_CASE("Each sliced cube's center of mass is its center, and the brim lowers the plate's printed one", "[GCodeProcessor]")
{
const bool copies = GENERATE(false, true);
INFO((copies ? "two copies of one cube" : "two cubes"));
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "outer_only" }, { "brim_width", 5 }, { "combine_brims", 0 } });
std::vector<TriangleMesh> cubes{ Test::cube(20) };
if (!copies)
cubes.emplace_back(Test::cube(20));
Print print;
Model model;
Test::init_print(std::move(cubes), print, model, config, nullptr, true, copies ? 2 : 1);
GCodeProcessorResult result;
Test::gcode(print, &result);
CHECK(result.body_masses.empty());
REQUIRE(result.object_masses.size() == 2);
for (const ModelObject *object : model.objects)
for (size_t instance = 0; instance < object->instances.size(); ++instance) {
const Vec3d center = object->instance_bounding_box(instance).center();
const auto mass = std::min_element(result.object_masses.begin(), result.object_masses.end(), [&center](const auto &l, const auto &r) {
return (center_of(l.total()) - center).squaredNorm() < (center_of(r.total()) - center).squaredNorm();
});
// Off the center only by the infill's alignment and the top and bottom shells.
const Vec3d part = center_of(mass->total());
CHECK_THAT(part.x(), Catch::Matchers::WithinAbs(center.x(), 0.5));
CHECK_THAT(part.y(), Catch::Matchers::WithinAbs(center.y(), 0.5));
CHECK_THAT(part.z(), Catch::Matchers::WithinAbs(center.z(), 1.));
// The outer walls' center lines run half a line inside the cube's sides, of copies touching each other too.
const BoundingBoxf3 box = object->instance_bounding_box(instance);
for (int axis = 0; axis < 3; ++axis) {
CHECK_THAT(mass->box.min[axis], Catch::Matchers::WithinAbs(box.min[axis], 0.3));
CHECK_THAT(mass->box.max[axis], Catch::Matchers::WithinAbs(box.max[axis], 0.3));
}
}
GCodeProcessorResult::ObjectMass::Sum objects;
for (const GCodeProcessorResult::ObjectMass &object : result.object_masses)
objects.add(object.total());
const GCodeProcessorResult::ObjectMass::Sum plate = result.plate_mass.total();
CHECK(plate.mass > objects.mass);
CHECK(center_of(plate).z() < center_of(objects).z());
}
TEST_CASE("Each cube's raft is its support, centered below it", "[GCodeProcessor]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "raft_layers", 3 } });
Print print;
Model model;
Test::init_print({ Test::cube(20), Test::cube(20) }, print, model, config);
GCodeProcessorResult result;
Test::gcode(print, &result);
REQUIRE(result.support_masses.size() == 2);
for (size_t i = 0; i < 2; ++i) {
const GCodeProcessorResult::ObjectMass::Sum support = result.support_masses[i].total();
const Vec3d object = center_of(result.object_masses[i].total());
REQUIRE(support.mass > 0.);
CHECK_THAT(center_of(support).x(), Catch::Matchers::WithinAbs(object.x(), 1.));
CHECK_THAT(center_of(support).y(), Catch::Matchers::WithinAbs(object.y(), 1.));
CHECK(center_of(support).z() < 1.);
}
}
TEST_CASE("A spiral vase cube counts all its extrusions, rising through each layer", "[GCodeProcessor]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "spiral_mode", 1 }, { "wall_loops", 1 },
{ "top_shell_layers", 0 }, { "sparse_infill_density", 0 } });
Print print;
Model model;
Test::init_print({ Test::cube(20) }, print, model, config);
GCodeProcessorResult result;
Test::gcode(print, &result);
REQUIRE(result.object_masses.size() == 1);
CHECK_THAT(result.object_masses.front().total().mass, Catch::Matchers::WithinRel(result.plate_mass.total().mass, 1e-6));
}
TEST_CASE("Each separate part of an assembly gets its center of mass, overlapping parts one", "[GCodeProcessor]")
{
const bool overlapping = GENERATE(false, true);
// Separated infills finds the bodies first, which the G-code export then takes.
const bool separated = GENERATE(false, true);
INFO((overlapping ? "overlapping parts" : "separate parts") << (separated ? ", separated infills" : ""));
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "separated_infills", separated ? 1 : 0 } });
TriangleMesh first = make_cube(20, 20, 20);
TriangleMesh second = make_cube(20, 20, 20);
first.translate(50, 50, 0);
second.translate(overlapping ? 60 : 90, 50, 0);
Print print;
Model model;
Test::init_print({ first }, print, model, config, nullptr, false);
model.objects.front()->add_volume(std::move(second), ModelVolumeType::MODEL_PART, false);
print.apply(model, config);
GCodeProcessorResult result;
Test::gcode(print, &result);
REQUIRE(result.object_masses.size() == 1);
CHECK(result.object_masses.front().assembly);
// One body is the object itself.
if (overlapping) {
CHECK(result.body_masses.empty());
return;
}
REQUIRE(result.body_masses.size() == 2);
CHECK(print.objects().front()->separated_body_bboxes().size() == (separated ? 2 : 0));
const ModelObject &object = *model.objects.front();
for (const ModelVolume *volume : object.volumes) {
const Vec3d center = volume->mesh().transformed_bounding_box(object.instances.front()->get_matrix() * volume->get_matrix()).center();
const auto body = std::min_element(result.body_masses.begin(), result.body_masses.end(), [&center](const auto &l, const auto &r) {
return (center_of(l.total()) - center).squaredNorm() < (center_of(r.total()) - center).squaredNorm();
});
const Vec3d part = center_of(body->total());
CHECK_THAT(part.x(), Catch::Matchers::WithinAbs(center.x(), 0.5));
CHECK_THAT(part.y(), Catch::Matchers::WithinAbs(center.y(), 0.5));
CHECK_THAT(part.z(), Catch::Matchers::WithinAbs(center.z(), 1.));
}
}
TEST_CASE("Each extrusion weighs its filament's density", "[GCodeProcessor]")
{
// Two like cubes, the second's filament three times as dense.
DynamicPrintConfig config = Test::multifilament_config(2, { { "filament_density", "1,3" }, { "skirt_loops", 0 }, { "brim_type", "no_brim" } });
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides{ { { "extruder", 1 } }, { { "extruder", 2 } } };
Print print;
Model model;
Test::init_print({ Test::cube(20), Test::cube(20) }, print, model, config, &overrides);
GCodeProcessorResult result;
Test::gcode(print, &result);
REQUIRE(result.object_masses.size() == 2);
std::vector<const GCodeProcessorResult::ObjectMass *> masses;
for (const ModelObject *object : model.objects) {
const Vec3d center = object->instance_bounding_box(0).center();
masses.emplace_back(&*std::min_element(result.object_masses.begin(), result.object_masses.end(), [&center](const auto &l, const auto &r) {
return (center_of(l.total()) - center).squaredNorm() < (center_of(r.total()) - center).squaredNorm();
}));
}
CHECK_THAT(masses[1]->total().mass / masses[0]->total().mass, Catch::Matchers::WithinRel(3., 0.02));
// The plate's center lies three quarters of the way to the dense cube.
const Vec3d plate = center_of(result.plate_mass.total());
const Vec3d light = center_of(masses[0]->total());
const Vec3d dense = center_of(masses[1]->total());
CHECK_THAT((plate - light).dot(dense - light) / (dense - light).squaredNorm(), Catch::Matchers::WithinAbs(0.75, 0.01));
}
-1
View File
@@ -65,7 +65,6 @@ add_executable(${_TEST_NAME}_tests
test_ordering_strategies.cpp
# test_png_io.cpp
test_indexed_triangle_set.cpp
test_connected_bodies.cpp
test_texture_displacement.cpp
test_instance_lock.cpp
../libnest2d/printer_parts.cpp
-154
View File
@@ -1,154 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ConnectedBodies.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/libslic3r.h"
#include <cstddef>
#include <utility>
#include <vector>
using namespace Slic3r;
using Catch::Matchers::WithinAbs;
using Catch::Matchers::WithinRel;
namespace {
ExPolygon rectangle(double x, double width) { return ExPolygon(Polygon::new_scale({ { x, 0. }, { x + width, 0. }, { x + width, 10. }, { x, 10. } })); }
} // namespace
TEST_CASE("Islands overlapping their neighbors' make one body", "[ConnectedBodies]")
{
const ExPolygons apart = { rectangle(0., 10.), rectangle(20., 10.) };
const ExPolygons bridge = { rectangle(0., 30.) };
const ExPolygons left = { rectangle(0., 10.) };
const ExPolygons right = { rectangle(20., 10.) };
size_t count = 0;
const std::vector<std::vector<size_t>> stacked = connected_bodies({ &apart, &apart }, count);
CHECK(count == 2);
CHECK(stacked[1][0] == stacked[0][0]);
CHECK(stacked[1][1] == stacked[0][1]);
connected_bodies({ &apart, &bridge, &apart }, count);
CHECK(count == 1);
// Neighbors that do not overlap stay apart even with one island a layer.
connected_bodies({ &left, &right }, count);
CHECK(count == 2);
}
TEST_CASE("The island locator tests the outlines only where boxes overlap", "[ConnectedBodies]")
{
const auto square = [](double from, double to) {
return Polygon::new_scale({ { from, from }, { to, from }, { to, to }, { from, to } });
};
Polygon hole = square(5., 25.);
hole.make_clockwise();
ExPolygon ring(square(0., 30.));
ring.holes.emplace_back(hole);
ExPolygon alone(square(40., 50.));
const ExPolygons islands = { ring, ExPolygon(square(10., 20.)), alone };
const IslandLocator locator(islands, scaled<coord_t>(1.));
const auto at = [](double x, double y) { return Point::new_scale(x, y); };
CHECK(locator.find(at(2., 2.)).first == 0);
CHECK(locator.find(at(15., 15.)).first == 1);
// In the ring's hole, outside the island within it, the nearest outline counts.
CHECK(locator.find(at(7., 15.)).first == 0);
CHECK(locator.find(at(9.5, 15.)).first == 1);
// An island no other box reaches takes the margin past its outline.
CHECK(locator.find(at(50.5, 45.)).first == 2);
// Unless strict, as for an island whose neighbor is another instance's: then it is only the nearest, 0.5 mm away.
const auto [nearest, distance] = locator.find(at(50.5, 45.), true);
CHECK(nearest == 2);
CHECK_THAT(distance, WithinRel(sqr(scaled<double>(0.5)), 1e-6));
CHECK_FALSE(locator.holds(2, at(50.5, 45.), true));
CHECK(locator.holds(2, at(50.5, 45.)));
CHECK_FALSE(locator.holds(1, at(7., 15.)));
CHECK(locator.find(at(35., 45.)).first == -1);
}
TEST_CASE("Separate solids are separate bodies", "[ConnectedBodies]")
{
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() }, { &cube, Geometry::translation_transform({ 20., 0., 0. }) } }, { 1., 1. }, {}, 100);
REQUIRE(bodies.size() == 2);
CHECK_THAT(bodies[0].mass, WithinRel(1000., 1e-4));
CHECK_THAT((bodies[0].center - Vec3d(5., 5., 5.)).norm(), WithinAbs(0., 1e-4));
CHECK_THAT(bodies[1].mass, WithinRel(1000., 1e-4));
CHECK_THAT((bodies[1].center - Vec3d(25., 5., 5.)).norm(), WithinAbs(0., 1e-4));
}
TEST_CASE("Overlapping solids are one body that counts the overlap once", "[ConnectedBodies]")
{
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() }, { &cube, Geometry::translation_transform({ 5., 0., 0. }) } }, { 1., 1. }, {}, 100);
// Their union is a 15 x 10 x 10 box, which spreads a^2 / 12 along each side a.
REQUIRE(bodies.size() == 1);
const SolidBody &body = bodies.front();
CHECK_THAT(body.mass, WithinRel(1500., 1e-4));
CHECK_THAT(body.volume, WithinRel(1500., 1e-4));
CHECK_THAT((body.center - Vec3d(7.5, 5., 5.)).norm(), WithinAbs(0., 1e-4));
const Matrix3d spread = Vec3d(225., 100., 100.).asDiagonal() * (1. / 12.);
CHECK_THAT((body.spread - spread).norm(), WithinAbs(0., 1e-4));
// Turned a quarter about z, the box spans what was its y in -x.
const BoundingBoxf3 box = body.bounding_box(Geometry::rotation_transform({ 0., 0., 0.5 * PI }));
CHECK_THAT((box.min - Vec3d(-10., 0., 0.)).norm(), WithinAbs(0., 1e-4));
CHECK_THAT((box.max - Vec3d(0., 15., 10.)).norm(), WithinAbs(0., 1e-4));
}
TEST_CASE("A negative solid is cut away from the body", "[ConnectedBodies]")
{
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
const indexed_triangle_set notch = its_make_cube(4., 4., 4.);
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() } }, { 1. }, { { &notch, Transform3d::Identity() } }, 100);
// A 10 mm cube centered at 5 less a 4 mm cube centered at 2, in each axis alike.
const double expected = (1000. * 5. - 64. * 2.) / (1000. - 64.);
REQUIRE(bodies.size() == 1);
CHECK_THAT(bodies[0].mass, WithinRel(1000. - 64., 1e-4));
CHECK_THAT((bodies[0].center - Vec3d(expected, expected, expected)).norm(), WithinAbs(0., 1e-4));
}
TEST_CASE("Each solid weighs its density, the later of two overlapping ones the overlap", "[ConnectedBodies]")
{
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
const MeshInPlace left{ &cube, Transform3d::Identity() };
const MeshInPlace right{ &cube, Geometry::translation_transform({ 5., 0., 0. }) };
// The right cube, three times as dense, prints the overlap from x 5 to 10.
auto bodies = solid_bodies({ left, right }, { 1., 3. }, {}, 100);
REQUIRE(bodies.size() == 1);
CHECK_THAT(bodies[0].mass, WithinRel(500. + 3. * 1000., 1e-4));
CHECK_THAT(bodies[0].volume, WithinRel(1500., 1e-4));
CHECK_THAT(bodies[0].center.x(), WithinAbs((500. * 2.5 + 3000. * 10.) / 3500., 1e-4));
// Listed the other way round, the left cube prints it.
bodies = solid_bodies({ right, left }, { 3., 1. }, {}, 100);
REQUIRE(bodies.size() == 1);
CHECK_THAT(bodies[0].mass, WithinRel(1000. + 3. * 500., 1e-4));
CHECK_THAT(bodies[0].center.x(), WithinAbs((1000. * 5. + 1500. * 12.5) / 2500., 1e-4));
}
TEST_CASE("Separate solids weigh their own densities", "[ConnectedBodies]")
{
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
const auto bodies = solid_bodies({ { &cube, Transform3d::Identity() }, { &cube, Geometry::translation_transform({ 20., 0., 0. }) } },
{ 1.24, 2. }, {}, 100);
REQUIRE(bodies.size() == 2);
CHECK_THAT(bodies[0].mass, WithinRel(1240., 1e-4));
CHECK_THAT(bodies[1].mass, WithinRel(2000., 1e-4));
}
@@ -12,16 +12,11 @@
#include <string>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "libslic3r/Geometry.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "test_utils.hpp"
using namespace Slic3r;
using Catch::Matchers::WithinAbs;
using Catch::Matchers::WithinRel;
TEST_CASE("Split empty mesh", "[its_split][its]") {
@@ -322,75 +317,3 @@ TEST_CASE("Simplified cube should not be empty.", "[its]")
its_quadric_edge_collapse(its, wanted_count, &max_error);
CHECK(!its.indices.empty());
}
TEST_CASE("A box far from the origin has its center of mass at its center and spreads as a box", "[its]")
{
indexed_triangle_set box = its_make_cube(10., 20., 30.);
for (Vec3f &v : box.vertices)
v += Vec3f(1000.f, 2000.f, 300.f);
const MassProperties solid = its_mass_properties(box);
CHECK_THAT(solid.volume, WithinRel(10. * 20. * 30., 1e-6));
CHECK_THAT(solid.mass, WithinRel(solid.volume, 1e-12));
CHECK_THAT(solid.center.x(), WithinAbs(1005., 1e-6));
CHECK_THAT(solid.center.y(), WithinAbs(2010., 1e-6));
CHECK_THAT(solid.center.z(), WithinAbs(315., 1e-6));
// A box of side a spreads a^2 / 12 along it.
const Matrix3d spread = Vec3d(100., 400., 900.).asDiagonal() * (1. / 12.);
CHECK_THAT((solid.spread - spread).norm(), WithinAbs(0., 1e-6));
}
TEST_CASE("The center of mass of a cone lies a quarter of its height above the base", "[its]")
{
// Neither the surface centroid nor the vertex average lands there.
const double h = 40.;
const MassProperties solid = its_mass_properties(its_make_cone(10., h));
CHECK(solid.volume > 0.);
CHECK_THAT(solid.center.z(), WithinAbs(h / 4., 1e-4));
CHECK_THAT(solid.center.x(), WithinAbs(0., 1e-4));
CHECK_THAT(solid.center.y(), WithinAbs(0., 1e-4));
// 3 h^2 / 80 along the axis.
CHECK_THAT(solid.spread(2, 2), WithinRel(3. * h * h / 80., 1e-4));
}
TEST_CASE("A cavity moves the center of mass away from it", "[its]")
{
indexed_triangle_set solid = its_make_cube(20., 20., 20.);
indexed_triangle_set cavity = its_make_cube(10., 10., 8.);
for (Vec3f &v : cavity.vertices)
v += Vec3f(5.f, 5.f, 10.f);
its_flip_triangles(cavity);
its_merge(solid, cavity);
const MassProperties hollow = its_mass_properties(solid);
// A 20 mm cube centered at z 10 less a 10x10x8 mm cavity centered at z 14.
CHECK_THAT(hollow.volume, WithinRel(8000. - 800., 1e-6));
CHECK_THAT(hollow.center.x(), WithinAbs(10., 1e-6));
CHECK_THAT(hollow.center.y(), WithinAbs(10., 1e-6));
CHECK_THAT(hollow.center.z(), WithinAbs((8000. * 10. - 800. * 14.) / (8000. - 800.), 1e-6));
}
TEST_CASE("The mass properties follow an affine transformation of the mesh", "[its]")
{
indexed_triangle_set cone = its_make_cone(10., 40.);
const MassProperties solid = its_mass_properties(cone);
const Transform3d trafo = Geometry::translation_transform({ 50., -20., 7. }) * Geometry::rotation_transform({ 0.3, -0.5, 1.2 }) *
Geometry::scale_transform({ 2., 0.5, 1.5 });
for (Vec3f &v : cone.vertices)
v = (trafo * v.cast<double>()).cast<float>();
const MassProperties moved = its_mass_properties(cone);
const MassProperties expected = solid.transformed(trafo);
CHECK_THAT(moved.volume, WithinRel(expected.volume, 1e-5));
CHECK_THAT(moved.mass, WithinRel(expected.mass, 1e-5));
CHECK_THAT((moved.center - expected.center).norm(), WithinAbs(0., 1e-4));
CHECK_THAT((moved.spread - expected.spread).norm(), WithinAbs(0., 1e-3));
}
TEST_CASE("Flipped faces keep the mass properties", "[its]")
{
indexed_triangle_set cone = its_make_cone(10., 40.);
const MassProperties solid = its_mass_properties(cone);
its_flip_triangles(cone);
const MassProperties flipped = its_mass_properties(cone);
CHECK_THAT(flipped.volume, WithinRel(solid.volume, 1e-9));
CHECK_THAT((flipped.center - solid.center).norm(), WithinAbs(0., 1e-9));
CHECK_THAT((flipped.spread - solid.spread).norm(), WithinAbs(0., 1e-9));
}
+12 -1
View File
@@ -133,7 +133,7 @@ TEST_CASE("Plugin host API reports unavailable GUI objects before Orca app initi
{
py::object host = import_orca_module().attr("host");
for (const char* function_name : { "preset_bundle", "plater", "model" }) {
for (const char* function_name : { "preset_bundle", "plater", "model", "app_info", "selected_printer" }) {
CAPTURE(function_name);
try {
host.attr(function_name)();
@@ -145,6 +145,17 @@ TEST_CASE("Plugin host API reports unavailable GUI objects before Orca app initi
}
}
TEST_CASE("Plugin host API reports no GL details before the 3D view exists", "[PluginHost][Python]")
{
py::object host = import_orca_module().attr("host");
CHECK(host.attr("gl_info")().is_none());
py::object plater_type = host.attr("Plater");
CHECK(has_attr(plater_type, "project_path"));
CHECK(has_attr(plater_type, "export_3mf_copy"));
}
TEST_CASE("Plugin host API exposes the UI module and guards it before Orca app initialization", "[PluginHost][Python]")
{
py::object host = import_orca_module().attr("host");