mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-18 13:10:54 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
948e6637fb | ||
|
|
40761ec6e2 | ||
|
|
186cd8e65f | ||
|
|
8bc0eec4aa | ||
|
|
ec30dad1cf | ||
|
|
f1428b72b8 | ||
|
|
3880f2c58e |
No files matched your search
@@ -574,8 +574,7 @@ DMABUF workaround and then branches on `GDK_BACKEND` (an `x11` prefix means the
|
|||||||
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
||||||
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
||||||
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
||||||
DMABUF renderer. On a hypervisor guest (`/proc/cpuinfo` `hypervisor` flag) and WebKitGTK >= 2.44 it sets
|
DMABUF renderer. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
||||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` alone, since virtual GPUs cannot back the hardware path either. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
|
||||||
is set, and sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) inside that branch when the linked
|
is set, and sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) inside that branch when the linked
|
||||||
WebKitGTK is older than 2.42: a forced X11 backend in a Wayland session is the only default-path case
|
WebKitGTK is older than 2.42: a forced X11 backend in a Wayland session is the only default-path case
|
||||||
that runs through XWayland, which `DISPLAY` and `WAYLAND_DISPLAY` alone do not tell you (a native
|
that runs through XWayland, which `DISPLAY` and `WAYLAND_DISPLAY` alone do not tell you (a native
|
||||||
|
|||||||
@@ -568,18 +568,13 @@ vue-resize workaround script and sets a persistent SQLite cookie store on the vi
|
|||||||
## WebKitGTK on Linux sessions
|
## WebKitGTK on Linux sessions
|
||||||
|
|
||||||
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
||||||
sets the DMABUF workarounds and then branches on `GDK_BACKEND`:
|
sets the NVIDIA DMABUF workaround and then branches on `GDK_BACKEND`:
|
||||||
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
|
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
|
||||||
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
|
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
|
||||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
|
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
|
||||||
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
|
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
|
||||||
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
|
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
|
||||||
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
|
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
|
||||||
- **Hypervisor guests**, same position: when `is_hypervisor_guest()` (the `hypervisor` flag in
|
|
||||||
`/proc/cpuinfo`) and WebKitGTK >= 2.44, it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` non-replacing.
|
|
||||||
Virtual GPUs (VirtualBox VMSVGA, VMware, QEMU) cannot back the hardware DMABUF path either, and
|
|
||||||
FORCE_SHM alone was confirmed to fix web views in VirtualBox; the `WEBKIT_FORCE_DMABUF_RENDERER` pair
|
|
||||||
is not needed without the Debian `isNVIDIA()` patch. The flag is x86-only, so ARM guests are not covered.
|
|
||||||
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
||||||
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`, and in that same branch it sets
|
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`, and in that same branch it sets
|
||||||
`WEBKIT_DISABLE_COMPOSITING_MODE=1` non-replacing when the linked WebKitGTK predates 2.42
|
`WEBKIT_DISABLE_COMPOSITING_MODE=1` non-replacing when the linked WebKitGTK predates 2.42
|
||||||
@@ -595,9 +590,9 @@ Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_
|
|||||||
(see `references/platforms.md`), not these environment variables.
|
(see `references/platforms.md`), not these environment variables.
|
||||||
|
|
||||||
**Pitfall.**
|
**Pitfall.**
|
||||||
- **Rule:** On NVIDIA and hypervisor guests reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
- **Rule:** On NVIDIA reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
||||||
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver and virtual GPUs cannot back WebKit's
|
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver cannot back WebKit's hardware
|
||||||
hardware DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
||||||
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
||||||
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
||||||
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ on:
|
|||||||
- 'scripts/test_build_win.ps1'
|
- 'scripts/test_build_win.ps1'
|
||||||
- 'build_release_macos.sh'
|
- 'build_release_macos.sh'
|
||||||
- 'scripts/build_preset_cache.*'
|
- 'scripts/build_preset_cache.*'
|
||||||
|
- 'signpath/**'
|
||||||
|
- 'scripts/*.ps1'
|
||||||
- 'scripts/flatpak/**'
|
- 'scripts/flatpak/**'
|
||||||
- 'scripts/msix/**'
|
- 'scripts/msix/**'
|
||||||
- 'tests/**'
|
- 'tests/**'
|
||||||
@@ -38,11 +40,13 @@ on:
|
|||||||
- 'version.inc'
|
- 'version.inc'
|
||||||
- ".github/workflows/build_*.yml"
|
- ".github/workflows/build_*.yml"
|
||||||
- ".github/workflows/unit_tests*.yml"
|
- ".github/workflows/unit_tests*.yml"
|
||||||
|
- 'signpath/**'
|
||||||
- 'build_linux.sh'
|
- 'build_linux.sh'
|
||||||
- 'build_win.bat'
|
- 'build_win.bat'
|
||||||
- 'scripts/test_build_win.ps1'
|
- 'scripts/test_build_win.ps1'
|
||||||
- 'build_release_macos.sh'
|
- 'build_release_macos.sh'
|
||||||
- 'scripts/build_preset_cache.*'
|
- 'scripts/build_preset_cache.*'
|
||||||
|
- 'scripts/*.ps1'
|
||||||
- 'scripts/flatpak/**'
|
- 'scripts/flatpak/**'
|
||||||
- 'scripts/msix/**'
|
- 'scripts/msix/**'
|
||||||
- 'tests/**'
|
- 'tests/**'
|
||||||
|
|||||||
@@ -424,6 +424,10 @@ jobs:
|
|||||||
"BUILD_DIR=build" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
"BUILD_DIR=build" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||||
"ARCH_SUFFIX=_x64" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
"ARCH_SUFFIX=_x64" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||||
}
|
}
|
||||||
|
# Release branches ship binaries and installers signed through SignPath with the
|
||||||
|
# release certificate. Every signing request waits for a manual approval in
|
||||||
|
# SignPath, so main and pull request builds stay unsigned.
|
||||||
|
"SIGN_WINDOWS=${{ github.repository == 'OrcaSlicer/OrcaSlicer' && startsWith(github.ref, 'refs/heads/release/') && !vars.SELF_HOSTED }}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||||
|
|
||||||
- name: setup MSVC
|
- name: setup MSVC
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
@@ -504,19 +508,129 @@ jobs:
|
|||||||
retention-days: 5
|
retention-days: 5
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
# Signs OrcaSlicer's own binaries and every bundled executable, DLL and Python
|
||||||
|
# extension module that ships without a signature; files their vendor signed, such
|
||||||
|
# as the Microsoft runtime, keep that signature. See signpath/README.md.
|
||||||
|
- name: Stage Windows binaries for SignPath
|
||||||
|
id: stage-windows-binaries
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$version = "${{ env.ver }}" -replace '^V', ''
|
||||||
|
./scripts/stage-for-signpath.ps1 -Bundle "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer" -Destination "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-binaries" -ProductVersion $version
|
||||||
|
"version=$version" | Out-File -Append -FilePath $env:GITHUB_OUTPUT -Encoding utf8
|
||||||
|
|
||||||
|
- name: Upload unsigned Windows binaries for SignPath
|
||||||
|
id: upload-windows-binaries
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: signpath_windows_binaries${{ env.ARCH_SUFFIX }}_unsigned
|
||||||
|
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-binaries
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
# Waits until the request is approved in SignPath.
|
||||||
|
- name: Sign Windows binaries with SignPath
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
uses: signpath/github-action-submit-signing-request@v2
|
||||||
|
with:
|
||||||
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||||
|
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||||
|
project-slug: OrcaSlicer
|
||||||
|
signing-policy-slug: release-signing
|
||||||
|
artifact-configuration-slug: windows-binaries-v1
|
||||||
|
github-artifact-id: ${{ steps.upload-windows-binaries.outputs.artifact-id }}
|
||||||
|
parameters: |
|
||||||
|
version: "${{ steps.stage-windows-binaries.outputs.version }}"
|
||||||
|
wait-for-completion: true
|
||||||
|
wait-for-completion-timeout-in-seconds: 10800
|
||||||
|
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries
|
||||||
|
|
||||||
|
- name: Verify Windows binary signatures
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries'
|
||||||
|
|
||||||
|
- name: Replace Windows binaries with signed output
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: ./scripts/restore-from-signpath.ps1 -SignedDirectory "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-binaries" -Bundle "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer"
|
||||||
|
|
||||||
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's
|
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's
|
||||||
# x86 emulation, packaging the native arm64 payload from build-arm64.
|
# x86 emulation, packaging the native arm64 payload from build-arm64.
|
||||||
|
# The installer packs the installed OrcaSlicer/ tree, which the portable zip and the
|
||||||
|
# MSIX are made from too, instead of re-running the install rules: those take
|
||||||
|
# CPython and uv from the dependency tree and the download cache, which keep the
|
||||||
|
# unsigned copies.
|
||||||
- name: Create installer Win
|
- name: Create installer Win
|
||||||
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||||
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
|
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
|
||||||
run: |
|
run: |
|
||||||
cpack -G NSIS
|
$bundle = "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer".Replace('\', '/')
|
||||||
|
cpack -G NSIS -D CPACK_INSTALL_CMAKE_PROJECTS= -D "CPACK_INSTALLED_DIRECTORIES=$bundle;."
|
||||||
|
|
||||||
|
# cpack stages what the installer packs under _CPack_Packages/<system>/NSIS/<package>;
|
||||||
|
# every executable and DLL there must carry a valid signature, ours or its vendor's.
|
||||||
|
- name: Verify every binary the installer packs is signed
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$payload = Get-Item "${{ github.workspace }}/${{ env.BUILD_DIR }}/_CPack_Packages/*/NSIS/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}"
|
||||||
|
./scripts/verify-authenticode.ps1 -ArtifactDirectory $payload.FullName
|
||||||
|
|
||||||
|
- name: Stage Windows installer for SignPath
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: ./scripts/stage-for-signpath.ps1 -Path "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe" -Destination "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-installer" -ProductVersion "${{ steps.stage-windows-binaries.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Upload unsigned Windows installer for SignPath
|
||||||
|
id: upload-windows-installer
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: signpath_windows_installer${{ env.ARCH_SUFFIX }}_unsigned
|
||||||
|
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/unsigned-installer
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Sign Windows installer with SignPath
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
uses: signpath/github-action-submit-signing-request@v2
|
||||||
|
with:
|
||||||
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||||
|
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||||
|
project-slug: OrcaSlicer
|
||||||
|
signing-policy-slug: release-signing
|
||||||
|
artifact-configuration-slug: windows-installer-v1
|
||||||
|
github-artifact-id: ${{ steps.upload-windows-installer.outputs.artifact-id }}
|
||||||
|
parameters: |
|
||||||
|
version: "${{ steps.stage-windows-binaries.outputs.version }}"
|
||||||
|
wait-for-completion: true
|
||||||
|
wait-for-completion-timeout-in-seconds: 3600
|
||||||
|
output-artifact-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer
|
||||||
|
|
||||||
|
- name: Verify Windows installer signature
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: ./scripts/verify-authenticode.ps1 -ArtifactDirectory '${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer' -Files 'OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe'
|
||||||
|
|
||||||
|
- name: Replace Windows installer with signed output
|
||||||
|
if: env.SIGN_WINDOWS == 'true'
|
||||||
|
shell: pwsh
|
||||||
|
run: Copy-Item -LiteralPath "${{ github.workspace }}/${{ env.BUILD_DIR }}/signpath/signed-installer/OrcaSlicer_Windows_Installer_${{ env.ver }}${{ env.ARCH_SUFFIX }}.exe" -Destination "${{ github.workspace }}/${{ env.BUILD_DIR }}" -Force
|
||||||
|
|
||||||
- name: Pack app
|
- name: Pack app
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
|
working-directory: ${{ github.workspace }}/${{ env.BUILD_DIR }}
|
||||||
shell: cmd
|
shell: pwsh
|
||||||
run: '"C:/Program Files/7-Zip/7z.exe" a -tzip OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer'
|
run: |
|
||||||
|
$zipPath = "OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable.zip"
|
||||||
|
Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue
|
||||||
|
& "C:/Program Files/7-Zip/7z.exe" a -tzip $zipPath "${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer"
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
- name: Pack PDB
|
- name: Pack PDB
|
||||||
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
|
if: runner.os == 'Windows' && inputs.arch != 'arm64' && !vars.SELF_HOSTED
|
||||||
@@ -530,6 +644,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable
|
name: OrcaSlicer_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }}_portable
|
||||||
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer
|
path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/OrcaSlicer
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload artifacts Win installer
|
- name: Upload artifacts Win installer
|
||||||
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||||
|
|||||||
+12
-13
@@ -288,19 +288,6 @@ if (APPLE)
|
|||||||
# Type"); Ninja/-Wall does not, and under -Werror it fails Xcode builds on code CI accepts.
|
# Type"); Ninja/-Wall does not, and under -Werror it fails Xcode builds on code CI accepts.
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION "NO")
|
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION "NO")
|
||||||
|
|
||||||
# The Xcode generator ignores CMAKE_<LANG>_COMPILER_LAUNCHER, so hand the launcher to
|
|
||||||
# Xcode's own Compiler Launcher setting, which wraps every clang compile (C, C++,
|
|
||||||
# Objective-C++ and the precompiled header) with one program: the C++ launcher wins when
|
|
||||||
# both are set. The setting takes a single program path, not a CMake launcher list.
|
|
||||||
# Xcode versions without it ignore it and build uncached.
|
|
||||||
if (CMAKE_GENERATOR STREQUAL "Xcode")
|
|
||||||
if (CMAKE_CXX_COMPILER_LAUNCHER)
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_C_COMPILER_LAUNCHER "${CMAKE_CXX_COMPILER_LAUNCHER}")
|
|
||||||
elseif (CMAKE_C_COMPILER_LAUNCHER)
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_C_COMPILER_LAUNCHER "${CMAKE_C_COMPILER_LAUNCHER}")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
|
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
|
||||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
||||||
@@ -1368,6 +1355,18 @@ set (CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
|||||||
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer")
|
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer")
|
||||||
set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer")
|
set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer")
|
||||||
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command
|
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command
|
||||||
|
# Version information of the installer, the same as the binaries it installs carry
|
||||||
|
# (src/dev-utils/platform/msw/OrcaSlicer-version.rc.in): SignPath only signs it when
|
||||||
|
# ProductName and ProductVersion match signpath/windows-installer-v1.xml. The NSIS strings
|
||||||
|
# are single-quoted because CPack writes this value into CPackConfig.cmake unescaped.
|
||||||
|
string (CONCAT CPACK_NSIS_DEFINES
|
||||||
|
"VIProductVersion '${ORCA_VERSION_MAJOR}.${ORCA_VERSION_MINOR}.${ORCA_VERSION_PATCH}.0'\n"
|
||||||
|
"VIFileVersion '${ORCA_VERSION_MAJOR}.${ORCA_VERSION_MINOR}.${ORCA_VERSION_PATCH}.0'\n"
|
||||||
|
"VIAddVersionKey /LANG=1033 'CompanyName' 'SoftFever'\n"
|
||||||
|
"VIAddVersionKey /LANG=1033 'FileDescription' '${SLIC3R_APP_NAME} Installer'\n"
|
||||||
|
"VIAddVersionKey /LANG=1033 'FileVersion' '${SoftFever_VERSION}'\n"
|
||||||
|
"VIAddVersionKey /LANG=1033 'ProductName' '${SLIC3R_APP_NAME}'\n"
|
||||||
|
"VIAddVersionKey /LANG=1033 'ProductVersion' '${SoftFever_VERSION}'\n")
|
||||||
|
|
||||||
set(CPACK_WIX_UPGRADE_GUID "058245e8-20e0-4a95-9ab7-1acfe17ad511")
|
set(CPACK_WIX_UPGRADE_GUID "058245e8-20e0-4a95-9ab7-1acfe17ad511")
|
||||||
set(CPACK_GENERATOR NSIS)
|
set(CPACK_GENERATOR NSIS)
|
||||||
|
|||||||
@@ -111,54 +111,6 @@ else
|
|||||||
export CMAKE_POLICY_COMPAT=""
|
export CMAKE_POLICY_COMPAT=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Compiler cache, as in build_linux.sh. The program comes from CMAKE_CCACHE, else from a
|
|
||||||
# launcher already set in the environment (the CI build sets ccache there), else sccache or
|
|
||||||
# ccache from PATH; one program serves both languages. Each run applies its own choice to the
|
|
||||||
# build directories, replacing a launcher an earlier run or a hand-run configure recorded.
|
|
||||||
CMAKE_CCACHE_ARGS=()
|
|
||||||
CCACHE_VAR=""
|
|
||||||
for var in CMAKE_CCACHE CMAKE_CXX_COMPILER_LAUNCHER CMAKE_C_COMPILER_LAUNCHER; do
|
|
||||||
if [ -n "${!var:-}" ]; then
|
|
||||||
CCACHE_VAR=$var
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ -n "$CCACHE_VAR" ]; then
|
|
||||||
CCACHE_PROGRAM=${!CCACHE_VAR}
|
|
||||||
echo "Checking ${CCACHE_VAR} environment variable for compiler cache program..."
|
|
||||||
CMAKE_CCACHE=$(command -v "${CCACHE_PROGRAM}") || {
|
|
||||||
echo "${CCACHE_VAR} environment variable is set to '${CCACHE_PROGRAM}' but it was not found in PATH."
|
|
||||||
CMAKE_CCACHE=""
|
|
||||||
}
|
|
||||||
elif command -v sccache >/dev/null 2>&1 ; then
|
|
||||||
CMAKE_CCACHE=$(command -v sccache)
|
|
||||||
elif command -v ccache >/dev/null 2>&1 ; then
|
|
||||||
CMAKE_CCACHE=$(command -v ccache)
|
|
||||||
fi
|
|
||||||
if [ -n "${CMAKE_CCACHE}" ] ; then
|
|
||||||
echo "${CMAKE_CCACHE} found, enabling compiler caching..."
|
|
||||||
CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_CCACHE}" -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CCACHE}")
|
|
||||||
# The same ccache settings as the CI build. Without this sloppiness ccache refuses every
|
|
||||||
# compile that uses the precompiled header, which is most of OrcaSlicer, and skips headers
|
|
||||||
# a checkout has just written. It is added to the sloppiness already configured, and
|
|
||||||
# CCACHE_SLOPPINESS or CCACHE_(NO)DEPEND set in the environment win.
|
|
||||||
if [[ "$(basename "${CMAKE_CCACHE}")" == ccache ]] ; then
|
|
||||||
if [[ -z "${CCACHE_SLOPPINESS+x}" ]] ; then
|
|
||||||
CCACHE_SLOPPINESS=$("${CMAKE_CCACHE}" --get-config sloppiness 2>/dev/null || true)
|
|
||||||
export CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS:+${CCACHE_SLOPPINESS},}pch_defines,time_macros,include_file_mtime,include_file_ctime"
|
|
||||||
fi
|
|
||||||
# Hash the includes the compiler reports instead of preprocessing every miss.
|
|
||||||
if [[ -z "${CCACHE_DEPEND+x}${CCACHE_NODEPEND+x}" ]] ; then
|
|
||||||
export CCACHE_DEPEND=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Note: ccache or sccache are not found. Install either of them for faster rebuilds."
|
|
||||||
# Clear a launcher recorded by an earlier configure. The entries must be set empty rather
|
|
||||||
# than removed: CMake fills a missing launcher entry from the environment.
|
|
||||||
CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER:STRING= -DCMAKE_CXX_COMPILER_LAUNCHER:STRING=)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Build params:"
|
echo "Build params:"
|
||||||
echo " - ARCH: $ARCH"
|
echo " - ARCH: $ARCH"
|
||||||
echo " - BUILD_CONFIG: $BUILD_CONFIG"
|
echo " - BUILD_CONFIG: $BUILD_CONFIG"
|
||||||
@@ -210,7 +162,6 @@ function build_deps() {
|
|||||||
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
|
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
||||||
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
||||||
"${CMAKE_CCACHE_ARGS[@]}" \
|
|
||||||
${CMAKE_POLICY_COMPAT}
|
${CMAKE_POLICY_COMPAT}
|
||||||
fi
|
fi
|
||||||
cmake --build . --config "$BUILD_CONFIG" --target deps
|
cmake --build . --config "$BUILD_CONFIG" --target deps
|
||||||
@@ -313,7 +264,6 @@ function build_slicer() {
|
|||||||
-DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \
|
-DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
||||||
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
||||||
"${CMAKE_CCACHE_ARGS[@]}" \
|
|
||||||
${CMAKE_POLICY_COMPAT}
|
${CMAKE_POLICY_COMPAT}
|
||||||
fi
|
fi
|
||||||
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
|
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
|
||||||
|
|||||||
@@ -45,10 +45,28 @@
|
|||||||
<color type="primary" scheme_preference="dark">#00695C</color>
|
<color type="primary" scheme_preference="dark">#00695C</color>
|
||||||
</branding>
|
</branding>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="2.5.0-dev" date="2026-06-22" type="development">
|
<release version="2.5.0-alpha" date="2026-10-14" type="development">
|
||||||
<url type="details">https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds</url>
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_5_0_alpha</url>
|
||||||
<description>
|
<description>
|
||||||
<p>See the release page for detailed changelog.</p>
|
<p>See the release page for detailed changelog of OrcaSlicer 2.5.0-alpha release.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.4.2" date="2026-07-26">
|
||||||
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_2</url>
|
||||||
|
<description>
|
||||||
|
<p>See the release page for detailed changelog of OrcaSlicer 2.4.2 release.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.4.1" date="2026-06-26">
|
||||||
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_1</url>
|
||||||
|
<description>
|
||||||
|
<p>See the release page for detailed changelog of OrcaSlicer 2.4.1 release.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.4.0" date="2026-06-20">
|
||||||
|
<url type="details">https://www.orcaslicer.com/wiki/releases/release_2_4_0</url>
|
||||||
|
<description>
|
||||||
|
<p>See the release page for detailed changelog of OrcaSlicer 2.4.0 release.</p>
|
||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
<release version="2.3.2" date="2025-03-23">
|
<release version="2.3.2" date="2025-03-23">
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||||
|
[string]$ArtifactDirectory
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Get-RelativePath {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Root,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$rootWithSeparator = $Root.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
|
||||||
|
$rootUri = [Uri]$rootWithSeparator
|
||||||
|
$pathUri = [Uri]$Path
|
||||||
|
|
||||||
|
[Uri]::UnescapeDataString($rootUri.MakeRelativeUri($pathUri).ToString()).Replace("/", [IO.Path]::DirectorySeparatorChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
$artifactRoot = (Resolve-Path -LiteralPath $ArtifactDirectory).Path
|
||||||
|
$binaries = Get-ChildItem -LiteralPath $artifactRoot -Recurse -File |
|
||||||
|
Where-Object { $_.Extension -in @(".exe", ".dll") } |
|
||||||
|
Sort-Object -Property FullName
|
||||||
|
|
||||||
|
if (-not $binaries) {
|
||||||
|
Write-Warning "No .exe or .dll files found under '$artifactRoot'."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$binaries | ForEach-Object {
|
||||||
|
$signature = Get-AuthenticodeSignature -LiteralPath $_.FullName
|
||||||
|
|
||||||
|
[pscustomobject]@{
|
||||||
|
RelativePath = Get-RelativePath -Root $artifactRoot -Path $_.FullName
|
||||||
|
Status = $signature.Status
|
||||||
|
SignatureType = $signature.SignatureType
|
||||||
|
Signer = if ($signature.SignerCertificate) { $signature.SignerCertificate.Subject } else { "" }
|
||||||
|
SignerThumbprint = if ($signature.SignerCertificate) { $signature.SignerCertificate.Thumbprint } else { "" }
|
||||||
|
Timestamped = [bool]$signature.TimeStamperCertificate
|
||||||
|
TimeStamper = if ($signature.TimeStamperCertificate) { $signature.TimeStamperCertificate.Subject } else { "" }
|
||||||
|
}
|
||||||
|
} | Format-Table -AutoSize
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Copies the files SignPath signed back over the originals in the portable bundle they
|
||||||
|
# were staged from with stage-for-signpath.ps1 -Bundle: the root of the signed artifact
|
||||||
|
# holds OrcaSlicer's own binaries, third-party/ the other files under their paths in the
|
||||||
|
# bundle.
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||||
|
[string]$SignedDirectory,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||||
|
[string]$Bundle
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$signedRoot = (Resolve-Path -LiteralPath $SignedDirectory).Path.TrimEnd('\', '/')
|
||||||
|
$thirdPartyPrefix = "third-party" + [IO.Path]::DirectorySeparatorChar
|
||||||
|
$files = Get-ChildItem -LiteralPath $signedRoot -Recurse -File
|
||||||
|
if (-not $files) {
|
||||||
|
throw "No signed files found in '$signedRoot'."
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($file in $files) {
|
||||||
|
$relative = $file.FullName.Substring($signedRoot.Length + 1)
|
||||||
|
if ($relative.StartsWith($thirdPartyPrefix, [StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
$relative = $relative.Substring($thirdPartyPrefix.Length)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every signed file replaces one that was staged, so a file without an original means
|
||||||
|
# the artifact does not have the layout stage-for-signpath.ps1 produces.
|
||||||
|
$target = Join-Path $Bundle $relative
|
||||||
|
if (-not (Test-Path -LiteralPath $target -PathType Leaf)) {
|
||||||
|
throw "Signed file '$relative' has no original in '$Bundle'."
|
||||||
|
}
|
||||||
|
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Restored $($files.Count) signed files into '$Bundle'."
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# Copies the files a SignPath signing request covers into the directory that request
|
||||||
|
# uploads, after checking their version information. SignPath refuses to sign a file
|
||||||
|
# whose ProductName or ProductVersion differs from its artifact configuration (see
|
||||||
|
# signpath/), and reports it only once the request has been approved, so the check runs
|
||||||
|
# before anything is submitted.
|
||||||
|
#
|
||||||
|
# -Path stages the given files at the root of -Destination.
|
||||||
|
#
|
||||||
|
# -Bundle stages a portable OrcaSlicer bundle for signpath/windows-binaries-v1.xml:
|
||||||
|
# OrcaSlicer's own binaries (-OwnBinaries) at the root, where their version information
|
||||||
|
# is checked, and every other executable, DLL and Python extension module that carries no
|
||||||
|
# signature under third-party/, at its path in the bundle. Files signed by their vendor,
|
||||||
|
# such as the Microsoft runtime, keep that signature. restore-from-signpath.ps1 copies the
|
||||||
|
# signed files back.
|
||||||
|
[CmdletBinding(DefaultParameterSetName = "Path")]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true, ParameterSetName = "Path")]
|
||||||
|
[string[]]$Path,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true, ParameterSetName = "Bundle")]
|
||||||
|
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||||
|
[string]$Bundle,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName = "Bundle")]
|
||||||
|
[string[]]$OwnBinaries = @("orca-slicer.exe", "OrcaSlicer.dll"),
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Destination,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ProductVersion,
|
||||||
|
|
||||||
|
[string]$ProductName = "OrcaSlicer"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Test-VersionInfo {
|
||||||
|
param([string]$File)
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $File -PathType Leaf)) {
|
||||||
|
throw "File to sign was not found: $File"
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = [Diagnostics.FileVersionInfo]::GetVersionInfo((Resolve-Path -LiteralPath $File).Path)
|
||||||
|
if ($info.ProductName -ne $ProductName -or $info.ProductVersion -ne $ProductVersion) {
|
||||||
|
throw "'$File' has ProductName '$($info.ProductName)' and ProductVersion '$($info.ProductVersion)'; SignPath expects '$ProductName' and '$ProductVersion'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
|
||||||
|
|
||||||
|
if ($PSCmdlet.ParameterSetName -eq "Path") {
|
||||||
|
foreach ($file in $Path) {
|
||||||
|
Test-VersionInfo $file
|
||||||
|
Copy-Item -LiteralPath $file -Destination $Destination -Force
|
||||||
|
Write-Host "Staged $(Split-Path -Leaf $file) ($ProductName $ProductVersion)"
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$bundleRoot = (Resolve-Path -LiteralPath $Bundle).Path.TrimEnd('\', '/')
|
||||||
|
foreach ($name in $OwnBinaries) {
|
||||||
|
$file = Join-Path $bundleRoot $name
|
||||||
|
Test-VersionInfo $file
|
||||||
|
Copy-Item -LiteralPath $file -Destination $Destination -Force
|
||||||
|
Write-Host "Staged $name ($ProductName $ProductVersion)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$thirdParty = Get-ChildItem -LiteralPath $bundleRoot -Recurse -File |
|
||||||
|
Where-Object { $_.Extension -in ".exe", ".dll", ".pyd" } |
|
||||||
|
Where-Object { $_.DirectoryName -ne $bundleRoot -or $_.Name -notin $OwnBinaries } |
|
||||||
|
Where-Object { (Get-AuthenticodeSignature -LiteralPath $_.FullName).Status -eq "NotSigned" }
|
||||||
|
|
||||||
|
foreach ($file in $thirdParty) {
|
||||||
|
$relative = $file.FullName.Substring($bundleRoot.Length + 1)
|
||||||
|
$target = Join-Path (Join-Path $Destination "third-party") $relative
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $target) | Out-Null
|
||||||
|
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
|
||||||
|
}
|
||||||
|
Write-Host "Staged $(@($thirdParty).Count) unsigned third-party files under third-party/"
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
|
||||||
|
[string]$ArtifactDirectory,
|
||||||
|
|
||||||
|
# Paths relative to -ArtifactDirectory. Without them every executable, DLL and Python
|
||||||
|
# extension module under it is verified.
|
||||||
|
[string[]]$Files,
|
||||||
|
|
||||||
|
[string]$SignToolPath,
|
||||||
|
|
||||||
|
# Accept signatures whose certificate chain terminates in an untrusted root.
|
||||||
|
# Required for the SignPath test certificate (self-signed). Do NOT pass this
|
||||||
|
# with the release certificate, so release builds enforce a fully trusted chain.
|
||||||
|
[switch]$AllowUntrustedRoot
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Resolve-SignToolPath {
|
||||||
|
param(
|
||||||
|
[string]$ExplicitPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($ExplicitPath) {
|
||||||
|
if (Test-Path -LiteralPath $ExplicitPath -PathType Leaf) {
|
||||||
|
return (Resolve-Path -LiteralPath $ExplicitPath).Path
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "SignTool was not found at '$ExplicitPath'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$fromPath = Get-Command -Name "signtool.exe" -ErrorAction SilentlyContinue
|
||||||
|
if ($fromPath) {
|
||||||
|
return $fromPath.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidateRoots = @(
|
||||||
|
"${env:ProgramFiles(x86)}\Windows Kits\10\bin",
|
||||||
|
"${env:ProgramFiles}\Windows Kits\10\bin"
|
||||||
|
) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container) }
|
||||||
|
|
||||||
|
foreach ($root in $candidateRoots) {
|
||||||
|
$candidate = Get-ChildItem -LiteralPath $root -Recurse -Filter "signtool.exe" -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.FullName -match "\\(x64|arm64)\\signtool\.exe$" } |
|
||||||
|
Sort-Object -Property FullName -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
|
||||||
|
if ($candidate) {
|
||||||
|
return $candidate.FullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "signtool.exe was not found. Install the Windows SDK or pass -SignToolPath."
|
||||||
|
}
|
||||||
|
|
||||||
|
$artifactRoot = (Resolve-Path -LiteralPath $ArtifactDirectory).Path.TrimEnd('\', '/')
|
||||||
|
$signtool = Resolve-SignToolPath -ExplicitPath $SignToolPath
|
||||||
|
|
||||||
|
if (-not $Files) {
|
||||||
|
$Files = Get-ChildItem -LiteralPath $artifactRoot -Recurse -File |
|
||||||
|
Where-Object { $_.Extension -in ".exe", ".dll", ".pyd" } |
|
||||||
|
ForEach-Object { $_.FullName.Substring($artifactRoot.Length + 1) }
|
||||||
|
if (-not $Files) {
|
||||||
|
throw "No executables or DLLs found in '$artifactRoot'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Using SignTool: $signtool"
|
||||||
|
Write-Host "Verifying Authenticode signatures of $(@($Files).Count) files in: $artifactRoot"
|
||||||
|
|
||||||
|
foreach ($relativePath in $Files) {
|
||||||
|
$filePath = Join-Path $artifactRoot $relativePath
|
||||||
|
if (-not (Test-Path -LiteralPath $filePath -PathType Leaf)) {
|
||||||
|
throw "Expected signed file was not found: $filePath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Capture signtool output without letting native stderr (redirected via 2>&1)
|
||||||
|
# raise a terminating NativeCommandError under $ErrorActionPreference = 'Stop'.
|
||||||
|
$previousErrorActionPreference = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
try {
|
||||||
|
$output = & $signtool verify /pa /all /tw /v $filePath 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$ErrorActionPreference = $previousErrorActionPreference
|
||||||
|
}
|
||||||
|
|
||||||
|
$signature = Get-AuthenticodeSignature -LiteralPath $filePath
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Verified $relativePath ($($signature.SignerCertificate.Subject))"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($AllowUntrustedRoot) {
|
||||||
|
# signtool interleaves its stdout (chain details) and stderr (the error)
|
||||||
|
# unpredictably, so the error text cannot be matched reliably. Use the
|
||||||
|
# structured Get-AuthenticodeSignature result instead: accept only when the
|
||||||
|
# file is genuinely signed and the sole problem is that the chain terminates
|
||||||
|
# in an untrusted root (i.e. the self-signed SignPath test certificate).
|
||||||
|
$isSigned = ($signature.SignatureType -eq "Authenticode") -and ($null -ne $signature.SignerCertificate)
|
||||||
|
$untrustedRootOnly = $signature.StatusMessage -match "terminated in a root certificate which is not trusted"
|
||||||
|
if ($isSigned -and $untrustedRootOnly) {
|
||||||
|
Write-Host "Accepted $relativePath ($($signature.SignerCertificate.Subject)): signed, but its certificate chains to an untrusted root (expected for the SignPath test certificate)."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The full chain and error details only for a file that did not verify.
|
||||||
|
$output | ForEach-Object { Write-Host " $_" }
|
||||||
|
throw "SignTool verification failed for '$relativePath' with exit code $exitCode."
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Authenticode verification passed."
|
||||||
|
|
||||||
|
# signtool exits non-zero for the untrusted test-cert root even when we accept
|
||||||
|
# it above, leaving $LASTEXITCODE = 1. The GitHub Actions pwsh wrapper exits
|
||||||
|
# with that lingering code, so reset it to report success explicitly.
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# SignPath configurations
|
||||||
|
|
||||||
|
Windows builds of `release/*` branches in `OrcaSlicer/OrcaSlicer` are code signed through [SignPath](https://about.signpath.io/), with the certificate the SignPath Foundation issues to open source projects. The XML files here are copies of the project's artifact configurations in SignPath, which the workflow refers to by slug only; a change to one has to be made in SignPath too, under the same slug.
|
||||||
|
|
||||||
|
Every signing request uses the `release-signing` policy and waits in SignPath until it is approved, so each Windows build of a release branch needs two approvals per architecture: the binaries, then the installer. Builds of `main` and pull requests are not signed.
|
||||||
|
|
||||||
|
## What gets signed
|
||||||
|
|
||||||
|
Every executable, DLL and Python extension module in the bundle ends up with a valid signature, so Windows features that check each loaded binary, such as Smart App Control, find none unsigned. Files their vendor already signed keep that signature: the Microsoft runtime DLLs and `WebView2Loader.dll` (Microsoft) and CPython's bundled OpenSSL and libffi (Python Software Foundation). Everything else is signed with the project's certificate: OrcaSlicer's own `orca-slicer.exe` and `OrcaSlicer.dll`, the libraries built from upstream sources (OCCT, FFmpeg, FreeType, GMP, MPFR, CPython), the downloaded `uv.exe`, and the NSIS installer.
|
||||||
|
|
||||||
|
The SignPath Foundation's conditions ask projects to sign only binaries built from their own source and to leave upstream projects' binaries unsigned, which they allow inside a signed installer. Signing the upstream libraries departs from that, by the maintainers' choice. Narrowing the signed set back to OrcaSlicer's own files means dropping the `third-party` file set from `windows-binaries-v1` and the third-party staging from `scripts/stage-for-signpath.ps1`.
|
||||||
|
|
||||||
|
The Foundation also requires metadata restrictions on signed files. OrcaSlicer's own binaries and the installer must have the ProductName `OrcaSlicer` and the ProductVersion passed in with the request, which the workflow takes from `SoftFever_VERSION` in `version.inc`. The binaries get that version information from `src/dev-utils/platform/msw/OrcaSlicer-version.rc.in`, the installer from `CPACK_NSIS_DEFINES` in `CMakeLists.txt`. The upstream files carry their own projects' metadata, so their file set has no restriction. `scripts/stage-for-signpath.ps1` checks the version information before a request is submitted, since SignPath only rejects a mismatch after the request has been approved.
|
||||||
|
|
||||||
|
## `windows-binaries-v1`
|
||||||
|
|
||||||
|
Signs the binaries of the installed bundle, `${{ env.BUILD_DIR }}/OrcaSlicer`. `scripts/stage-for-signpath.ps1 -Bundle` stages them for upload: `orca-slicer.exe` and `OrcaSlicer.dll` at the root of the artifact ZIP, where the metadata restrictions apply, and every other unsigned `.exe`, `.dll` and `.pyd` under `third-party/`, at its path in the bundle. `scripts/restore-from-signpath.ps1` copies the signed files back over the originals. The portable ZIP, the MSIX package and the installer are all packed from that bundle; `cpack` is pointed at it with `CPACK_INSTALLED_DIRECTORIES` rather than re-running the install rules, which would take CPython and `uv.exe` from their unsigned sources again. A check after `cpack` verifies that every binary in the installer's payload is signed.
|
||||||
|
|
||||||
|
## `windows-installer-v1`
|
||||||
|
|
||||||
|
Signs the NSIS installer `cpack` produces, `OrcaSlicer_Windows_Installer_<version>_<arch>.exe`, uploaded as the only file of its artifact. The uninstaller NSIS writes during installation is not signed.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||||
|
<parameters>
|
||||||
|
<parameter name="version" required="true" />
|
||||||
|
</parameters>
|
||||||
|
<zip-file>
|
||||||
|
<pe-file-set product-name="OrcaSlicer" product-version="${version}">
|
||||||
|
<include path="orca-slicer.exe" />
|
||||||
|
<include path="OrcaSlicer.dll" />
|
||||||
|
<for-each>
|
||||||
|
<authenticode-sign />
|
||||||
|
</for-each>
|
||||||
|
</pe-file-set>
|
||||||
|
<directory path="third-party">
|
||||||
|
<pe-file-set>
|
||||||
|
<include path="**/*.exe" min-matches="0" max-matches="unbounded" />
|
||||||
|
<include path="**/*.dll" min-matches="0" max-matches="unbounded" />
|
||||||
|
<include path="**/*.pyd" min-matches="0" max-matches="unbounded" />
|
||||||
|
<for-each>
|
||||||
|
<authenticode-sign />
|
||||||
|
</for-each>
|
||||||
|
</pe-file-set>
|
||||||
|
</directory>
|
||||||
|
</zip-file>
|
||||||
|
</artifact-configuration>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||||
|
<parameters>
|
||||||
|
<parameter name="version" required="true" />
|
||||||
|
</parameters>
|
||||||
|
<zip-file>
|
||||||
|
<pe-file path="OrcaSlicer_Windows_Installer_*.exe" product-name="OrcaSlicer" product-version="${version}">
|
||||||
|
<authenticode-sign />
|
||||||
|
</pe-file>
|
||||||
|
</zip-file>
|
||||||
|
</artifact-configuration>
|
||||||
+6
-2
@@ -140,7 +140,11 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.rc.
|
|||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.manifest @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.manifest @ONLY)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_library(OrcaSlicer SHARED OrcaSlicer.cpp OrcaSlicer.hpp)
|
foreach (_orca_rc_binary IN ITEMS orca-slicer.exe OrcaSlicer.dll)
|
||||||
|
set(ORCA_RC_ORIGINAL_FILENAME ${_orca_rc_binary})
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/msw/OrcaSlicer-version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/${_orca_rc_binary}.version.rc @ONLY)
|
||||||
|
endforeach ()
|
||||||
|
add_library(OrcaSlicer SHARED OrcaSlicer.cpp OrcaSlicer.hpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.dll.version.rc)
|
||||||
else ()
|
else ()
|
||||||
add_executable(OrcaSlicer OrcaSlicer.cpp OrcaSlicer.hpp)
|
add_executable(OrcaSlicer OrcaSlicer.cpp OrcaSlicer.hpp)
|
||||||
endif ()
|
endif ()
|
||||||
@@ -216,7 +220,7 @@ if (WIN32)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(OrcaSlicer_app_gui WIN32 OrcaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.rc)
|
add_executable(OrcaSlicer_app_gui WIN32 OrcaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.rc ${CMAKE_CURRENT_BINARY_DIR}/orca-slicer.exe.version.rc)
|
||||||
# Generate debug symbols even in release mode.
|
# Generate debug symbols even in release mode.
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||||
|
|||||||
@@ -1126,19 +1126,6 @@ static void load_downward_settings_list_from_config(std::string config_file, std
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __WXGTK__
|
|
||||||
// Virtual GPUs (VirtualBox, VMware, QEMU) cannot back WebKit's hardware DMABUF renderer.
|
|
||||||
static bool is_hypervisor_guest()
|
|
||||||
{
|
|
||||||
std::ifstream cpuinfo("/proc/cpuinfo");
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(cpuinfo, line))
|
|
||||||
if (boost::starts_with(line, "flags"))
|
|
||||||
return line.find(" hypervisor") != std::string::npos;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int CLI::run(int argc, char **argv)
|
int CLI::run(int argc, char **argv)
|
||||||
{
|
{
|
||||||
// Mark the main thread for the debugger and for runtime checks.
|
// Mark the main thread for the debugger and for runtime checks.
|
||||||
@@ -1189,9 +1176,6 @@ int CLI::run(int argc, char **argv)
|
|||||||
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
|
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_hypervisor_guest() && WebView::SupportsForceSharedMemory())
|
|
||||||
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /* replace */ false);
|
|
||||||
|
|
||||||
if (x11_opt_in) {
|
if (x11_opt_in) {
|
||||||
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
||||||
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Version information of orca-slicer.exe and OrcaSlicer.dll, configured once per binary.
|
||||||
|
// SignPath only signs a binary whose ProductName and ProductVersion match its artifact
|
||||||
|
// configuration (signpath/windows-binaries-v1.xml), which the workflow passes the
|
||||||
|
// SoftFever_VERSION from version.inc.
|
||||||
|
1 VERSIONINFO
|
||||||
|
FILEVERSION @ORCA_VERSION_MAJOR@,@ORCA_VERSION_MINOR@,@ORCA_VERSION_PATCH@,0
|
||||||
|
PRODUCTVERSION @ORCA_VERSION_MAJOR@,@ORCA_VERSION_MINOR@,@ORCA_VERSION_PATCH@,0
|
||||||
|
{
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
{
|
||||||
|
BLOCK "040904E4"
|
||||||
|
{
|
||||||
|
VALUE "CompanyName", "SoftFever"
|
||||||
|
VALUE "FileDescription", "@SLIC3R_APP_NAME@"
|
||||||
|
VALUE "FileVersion", "@SoftFever_VERSION@"
|
||||||
|
VALUE "ProductName", "@SLIC3R_APP_NAME@"
|
||||||
|
VALUE "ProductVersion", "@SoftFever_VERSION@"
|
||||||
|
VALUE "InternalName", "@SLIC3R_APP_NAME@"
|
||||||
|
VALUE "OriginalFilename", "@ORCA_RC_ORIGINAL_FILENAME@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
{
|
||||||
|
VALUE "Translation", 0x409, 1252
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,2 @@
|
|||||||
1 VERSIONINFO
|
|
||||||
FILEVERSION @SLIC3R_VERSION@
|
|
||||||
PRODUCTVERSION @SLIC3R_VERSION@
|
|
||||||
{
|
|
||||||
BLOCK "StringFileInfo"
|
|
||||||
{
|
|
||||||
BLOCK "040904E4"
|
|
||||||
{
|
|
||||||
VALUE "CompanyName", "SoftFever"
|
|
||||||
VALUE "FileDescription", "@SLIC3R_APP_NAME@"
|
|
||||||
VALUE "FileVersion", "@SLIC3R_BUILD_ID@"
|
|
||||||
VALUE "ProductName", "@SLIC3R_APP_NAME@"
|
|
||||||
VALUE "ProductVersion", "@SLIC3R_BUILD_ID@"
|
|
||||||
VALUE "InternalName", "@SLIC3R_APP_NAME@"
|
|
||||||
VALUE "LegalCopyright", ""
|
|
||||||
VALUE "OriginalFilename", "orca-slicer.exe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BLOCK "VarFileInfo"
|
|
||||||
{
|
|
||||||
VALUE "Translation", 0x409, 1252
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2 ICON "@SLIC3R_RESOURCES_DIR@/images/OrcaSlicer.ico"
|
2 ICON "@SLIC3R_RESOURCES_DIR@/images/OrcaSlicer.ico"
|
||||||
1 24 "OrcaSlicer.manifest"
|
1 24 "OrcaSlicer.manifest"
|
||||||
@@ -812,8 +812,7 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpa
|
|||||||
* Get the order constraints of the insets when printing walls per region / hole.
|
* Get the order constraints of the insets when printing walls per region / hole.
|
||||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||||
*
|
*
|
||||||
* Odd walls follow the same order as even ones, so that the centre line of a narrow wall is an inner wall
|
* Odd walls should always go after their enclosing wall polygons.
|
||||||
* like any other when printing inner to outer.
|
|
||||||
*
|
*
|
||||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||||
*/
|
*/
|
||||||
@@ -887,7 +886,12 @@ WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector<
|
|||||||
continue; // not directly adjacent
|
continue; // not directly adjacent
|
||||||
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
||||||
continue; // points are too far away from each other
|
continue; // points are too far away from each other
|
||||||
if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
if (here->is_odd || nearby->is_odd) {
|
||||||
|
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
|
||||||
|
order_requirements.emplace(std::make_pair(nearby, here));
|
||||||
|
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
|
||||||
|
order_requirements.emplace(std::make_pair(here, nearby));
|
||||||
|
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
||||||
order_requirements.emplace(std::make_pair(nearby, here));
|
order_requirements.emplace(std::make_pair(nearby, here));
|
||||||
} else {
|
} else {
|
||||||
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
||||||
|
|||||||
@@ -3985,16 +3985,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
||||||
this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||||
this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||||
// The final print statistics are known only after the whole G-code is generated, so they are filled in during post-processing too.
|
|
||||||
// The running extruded_* totals are left alone; profiles use them as numbers.
|
|
||||||
this->placeholder_parser().set("used_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
|
||||||
this->placeholder_parser().set("print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
|
|
||||||
this->placeholder_parser().set("normal_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
|
|
||||||
this->placeholder_parser().set("silent_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Silent_Print_Time_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_weight", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Weight_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Cost_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_wipe_tower_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Cost_Placeholder)));
|
|
||||||
this->placeholder_parser().set("total_wipe_tower_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Filament_Placeholder)));
|
|
||||||
|
|
||||||
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
||||||
update_placeholder_parser_with_variant_params();
|
update_placeholder_parser_with_variant_params();
|
||||||
|
|||||||
@@ -120,13 +120,7 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
|||||||
"@PRINT_TIME_HOUR@",
|
"@PRINT_TIME_HOUR@",
|
||||||
"@PRINT_TIME_MINUTE@",
|
"@PRINT_TIME_MINUTE@",
|
||||||
"@PRINT_TIME_SEC@",
|
"@PRINT_TIME_SEC@",
|
||||||
"@USED_FILAMENT_LENGTH@",
|
"@USED_FILAMENT_LENGTH@"
|
||||||
"@NORMAL_PRINT_TIME@",
|
|
||||||
"@SILENT_PRINT_TIME@",
|
|
||||||
"@TOTAL_WEIGHT@",
|
|
||||||
"@TOTAL_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_FILAMENT@"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
|
const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
|
||||||
@@ -153,13 +147,7 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
|
|||||||
"@PRINT_TIME_HOUR@",
|
"@PRINT_TIME_HOUR@",
|
||||||
"@PRINT_TIME_MINUTE@",
|
"@PRINT_TIME_MINUTE@",
|
||||||
"@PRINT_TIME_SEC@",
|
"@PRINT_TIME_SEC@",
|
||||||
"@USED_FILAMENT_LENGTH@",
|
"@USED_FILAMENT_LENGTH@"
|
||||||
"@NORMAL_PRINT_TIME@",
|
|
||||||
"@SILENT_PRINT_TIME@",
|
|
||||||
"@TOTAL_WEIGHT@",
|
|
||||||
"@TOTAL_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_COST@",
|
|
||||||
"@TOTAL_WIPE_TOWER_FILAMENT@"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1276,22 +1264,19 @@ void GCodeProcessor::run_post_process()
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process inline placeholders (print_time_*, used_filament_length and the print statistics known only after export)
|
// Process inline placeholders (print_time_total_sec, print_time_day, print_time_hour, print_time_minute, print_time_sec and used_filament_length)
|
||||||
auto process_inline_placeholders = [&](std::string& gcode_line) {
|
auto process_inline_placeholders = [&](std::string& gcode_line) {
|
||||||
bool processed = false;
|
bool processed = false;
|
||||||
// Every inline placeholder contains '@', so a line without one has nothing to replace.
|
// Every inline placeholder contains '@', so a line without one has nothing to replace.
|
||||||
if (gcode_line.find('@') == std::string::npos)
|
if (gcode_line.find('@') == std::string::npos)
|
||||||
return processed;
|
return processed;
|
||||||
|
|
||||||
auto replace_tag = [&gcode_line, &processed](ETags tag, const std::string& value) {
|
const std::string& print_time_total_placeholder = reserved_tag(ETags::Print_Time_Total_Sec_Placeholder);
|
||||||
const std::string& placeholder = reserved_tag(tag);
|
const std::string& print_time_day_placeholder = reserved_tag(ETags::Print_Time_Day_Placeholder);
|
||||||
size_t pos = gcode_line.find(placeholder);
|
const std::string& print_time_hour_placeholder = reserved_tag(ETags::Print_Time_Hour_Placeholder);
|
||||||
while (pos != std::string::npos) {
|
const std::string& print_time_minute_placeholder = reserved_tag(ETags::Print_Time_Minute_Placeholder);
|
||||||
gcode_line.replace(pos, placeholder.length(), value);
|
const std::string& print_time_sec_placeholder = reserved_tag(ETags::Print_Time_Sec_Placeholder);
|
||||||
processed = true;
|
const std::string& used_filament_placeholder = reserved_tag(ETags::Used_Filament_Length_Placeholder);
|
||||||
pos = gcode_line.find(placeholder, pos + value.length());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
double print_time_total_sec = m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
|
double print_time_total_sec = m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
|
||||||
if (print_time_total_sec < 0.0)
|
if (print_time_total_sec < 0.0)
|
||||||
@@ -1304,31 +1289,70 @@ void GCodeProcessor::run_post_process()
|
|||||||
int print_time_minute = (day_remainder_seconds % 3600) / 60;
|
int print_time_minute = (day_remainder_seconds % 3600) / 60;
|
||||||
int print_time_sec = day_remainder_seconds % 60;
|
int print_time_sec = day_remainder_seconds % 60;
|
||||||
|
|
||||||
double total_filament_mm = 0.0;
|
// Replace print_time_total_sec
|
||||||
for (const auto& mm : filament_mm) {
|
size_t pos = gcode_line.find(print_time_total_placeholder);
|
||||||
total_filament_mm += mm;
|
while (pos != std::string::npos) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%.2f", print_time_total_sec);
|
||||||
|
gcode_line.replace(pos, print_time_total_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(print_time_total_placeholder, pos + strlen(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
replace_tag(ETags::Print_Time_Total_Sec_Placeholder, Slic3r::float_to_string_decimal_point(print_time_total_sec, 2));
|
// Replace print_time_day
|
||||||
replace_tag(ETags::Print_Time_Day_Placeholder, std::to_string(print_time_day));
|
pos = gcode_line.find(print_time_day_placeholder);
|
||||||
replace_tag(ETags::Print_Time_Hour_Placeholder, std::to_string(print_time_hour));
|
while (pos != std::string::npos) {
|
||||||
replace_tag(ETags::Print_Time_Minute_Placeholder, std::to_string(print_time_minute));
|
char buf[64];
|
||||||
replace_tag(ETags::Print_Time_Sec_Placeholder, std::to_string(print_time_sec));
|
sprintf(buf, "%d", print_time_day);
|
||||||
replace_tag(ETags::Used_Filament_Length_Placeholder,
|
gcode_line.replace(pos, print_time_day_placeholder.length(), buf);
|
||||||
Slic3r::float_to_string_decimal_point(total_filament_mm / 1000.0, 2)); // Convert mm to m
|
processed = true;
|
||||||
// Same values as the print statistics that filename_format gets (PrintStatistics::config(), filled in by
|
pos = gcode_line.find(print_time_day_placeholder, pos + strlen(buf));
|
||||||
// update_print_estimated_stats() in GCode.cpp), which runs only after this post-processing.
|
}
|
||||||
replace_tag(ETags::Normal_Print_Time_Placeholder, get_time_dhm(PrintEstimatedStatistics::ETimeMode::Normal));
|
|
||||||
replace_tag(ETags::Silent_Print_Time_Placeholder, is_stealth_time_estimator_enabled() ?
|
// Replace print_time_hour
|
||||||
get_time_dhm(PrintEstimatedStatistics::ETimeMode::Stealth) :
|
pos = gcode_line.find(print_time_hour_placeholder);
|
||||||
short_time("N/A"));
|
while (pos != std::string::npos) {
|
||||||
const double total_cost = filament_total_cost + m_print->config().time_cost.value * print_time_total_sec / 3600.0;
|
char buf[64];
|
||||||
replace_tag(ETags::Total_Weight_Placeholder, Slic3r::float_to_string_decimal_point(filament_total_g, 2));
|
sprintf(buf, "%d", print_time_hour);
|
||||||
replace_tag(ETags::Total_Cost_Placeholder, Slic3r::float_to_string_decimal_point(total_cost, 2));
|
gcode_line.replace(pos, print_time_hour_placeholder.length(), buf);
|
||||||
replace_tag(ETags::Total_Wipe_Tower_Cost_Placeholder,
|
processed = true;
|
||||||
Slic3r::float_to_string_decimal_point(m_print->print_statistics().total_wipe_tower_cost, 2));
|
pos = gcode_line.find(print_time_hour_placeholder, pos + strlen(buf));
|
||||||
replace_tag(ETags::Total_Wipe_Tower_Filament_Placeholder,
|
}
|
||||||
Slic3r::float_to_string_decimal_point(m_print->print_statistics().total_wipe_tower_filament, 2));
|
|
||||||
|
// Replace print_time_minute
|
||||||
|
pos = gcode_line.find(print_time_minute_placeholder);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%d", print_time_minute);
|
||||||
|
gcode_line.replace(pos, print_time_minute_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(print_time_minute_placeholder, pos + strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace print_time_sec
|
||||||
|
pos = gcode_line.find(print_time_sec_placeholder);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%d", print_time_sec);
|
||||||
|
gcode_line.replace(pos, print_time_sec_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(print_time_sec_placeholder, pos + strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace used_filament_length
|
||||||
|
pos = gcode_line.find(used_filament_placeholder);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
double total_filament_mm = 0.0;
|
||||||
|
for (const auto& mm : filament_mm) {
|
||||||
|
total_filament_mm += mm;
|
||||||
|
}
|
||||||
|
double used_filament_length = total_filament_mm / 1000.0; // Convert mm to m
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%.2f", used_filament_length);
|
||||||
|
gcode_line.replace(pos, used_filament_placeholder.length(), buf);
|
||||||
|
processed = true;
|
||||||
|
pos = gcode_line.find(used_filament_placeholder, pos + strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -601,12 +601,6 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
|||||||
Print_Time_Minute_Placeholder,
|
Print_Time_Minute_Placeholder,
|
||||||
Print_Time_Sec_Placeholder,
|
Print_Time_Sec_Placeholder,
|
||||||
Used_Filament_Length_Placeholder,
|
Used_Filament_Length_Placeholder,
|
||||||
Normal_Print_Time_Placeholder,
|
|
||||||
Silent_Print_Time_Placeholder,
|
|
||||||
Total_Weight_Placeholder,
|
|
||||||
Total_Cost_Placeholder,
|
|
||||||
Total_Wipe_Tower_Cost_Placeholder,
|
|
||||||
Total_Wipe_Tower_Filament_Placeholder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; }
|
static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; }
|
||||||
|
|||||||
@@ -62,12 +62,7 @@ public:
|
|||||||
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
||||||
unsigned short depth;
|
unsigned short depth;
|
||||||
// ORCA: an external hole perimeter touching the next outer wall all around, with nothing between them.
|
// ORCA: an external hole perimeter touching the next outer wall all around, with nothing between them.
|
||||||
// E.g. a 1mm tube wall with 0.5mm walls: 2 lines, the contour's outer wall and the hole's.
|
|
||||||
bool is_thin_wall_hole = false;
|
bool is_thin_wall_hole = false;
|
||||||
// ORCA: an external hole perimeter with no room for an inner wall beside it, at most gap fill. Every thin wall
|
|
||||||
// hole is one, and so is e.g. a 1.5mm tube wall with 0.5mm walls: 3 lines, the two outer walls and gap fill
|
|
||||||
// between them. A 2mm tube wall has room for an inner wall a side and is not. Only set for Inner/Outer/Inner.
|
|
||||||
bool is_narrow_wall_hole = false;
|
|
||||||
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
||||||
std::vector<PerimeterGeneratorLoop> children;
|
std::vector<PerimeterGeneratorLoop> children;
|
||||||
|
|
||||||
@@ -577,8 +572,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
|||||||
extrusion_coll.append(std::move(extrusion_loop));
|
extrusion_coll.append(std::move(extrusion_loop));
|
||||||
// Orca: Reverse the order of paths for thin wall holes. We define thin wall hole as a hole with only one perimeter.
|
// Orca: Reverse the order of paths for thin wall holes. We define thin wall hole as a hole with only one perimeter.
|
||||||
const bool thin_wall_hole = !pg_extrusion.is_contour && pg_extrusions.size() == 2;
|
const bool thin_wall_hole = !pg_extrusion.is_contour && pg_extrusions.size() == 2;
|
||||||
if (thin_wall_hole && perimeter_generator.config->wall_sequence != WallSequence::OuterInner &&
|
if (thin_wall_hole && perimeter_generator.config->wall_sequence != WallSequence::OuterInner)
|
||||||
!perimeter_generator.outer_walls_first_for_brim())
|
|
||||||
std::reverse(extrusion_coll.entities.begin(), extrusion_coll.entities.end());
|
std::reverse(extrusion_coll.entities.begin(), extrusion_coll.entities.end());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1696,24 +1690,13 @@ void PerimeterGenerator::process_classic()
|
|||||||
|
|
||||||
last = std::move(offsets);
|
last = std::move(offsets);
|
||||||
|
|
||||||
// ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall, a
|
// ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall.
|
||||||
// narrow wall hole none for an inner wall, the room the next loop is offset by below.
|
|
||||||
if (i == 0 && ! holes[0].empty()) {
|
if (i == 0 && ! holes[0].empty()) {
|
||||||
auto reach = [&last](double room) {
|
const float room = float(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
|
||||||
return to_polygons(offset2_ex(last, -float(room), float(room) + float(SCALED_EPSILON)));
|
const Polygons reach = to_polygons(offset2_ex(last, -room, room + float(SCALED_EPSILON)));
|
||||||
};
|
for (PerimeterGeneratorLoop &hole : holes[0])
|
||||||
auto out_of_reach = [](const Polygon &hole, const Polygons &reach) {
|
hole.is_thin_wall_hole = intersection_pl(Polylines{ hole.polygon.split_at_first_point() },
|
||||||
return intersection_pl(Polylines{ hole.split_at_first_point() },
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(reach, get_extents(hole.polygon).inflated(SCALED_EPSILON))).empty();
|
||||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(reach, get_extents(hole).inflated(SCALED_EPSILON))).empty();
|
|
||||||
};
|
|
||||||
// Only Inner/Outer/Inner orders narrow wall holes apart.
|
|
||||||
const bool narrow_holes_ordered = this->config->wall_sequence == WallSequence::InnerOuterInner;
|
|
||||||
const Polygons gap_fill_reach = reach(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
|
|
||||||
const Polygons inner_wall_reach = narrow_holes_ordered ? reach(ext_perimeter_spacing2 + min_spacing / 2.) : Polygons();
|
|
||||||
for (PerimeterGeneratorLoop &hole : holes[0]) {
|
|
||||||
hole.is_thin_wall_hole = out_of_reach(hole.polygon, gap_fill_reach);
|
|
||||||
hole.is_narrow_wall_hole = narrow_holes_ordered && out_of_reach(hole.polygon, inner_wall_reach);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: refer to superslicer
|
//BBS: refer to superslicer
|
||||||
@@ -1852,16 +1835,18 @@ void PerimeterGenerator::process_classic()
|
|||||||
|
|
||||||
// if brim will be printed, reverse the order of perimeters so that
|
// if brim will be printed, reverse the order of perimeters so that
|
||||||
// we continue inwards after having finished the brim
|
// we continue inwards after having finished the brim
|
||||||
|
// TODO: add test for perimeter order
|
||||||
bool is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner;
|
bool is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner;
|
||||||
// Orca: a contour with only a narrow wall hole beside it has nothing to sandwich, and prints the hole first,
|
if (is_outer_wall_first ||
|
||||||
// then the contour, then the gap fill between them, as Arachne does.
|
//BBS: always print outer wall first when there indeed has brim.
|
||||||
const PerimeterGeneratorLoops &island_loops = contours.front();
|
// btLeadingEdgeOnly is an outer brim too (a belt brim at the part's first contact).
|
||||||
const bool narrow_wall_hole_island = island_loops.size() == 1 && island_loops.front().children.size() == 1 &&
|
(this->layer_id == 0 &&
|
||||||
island_loops.front().children.front().is_narrow_wall_hole;
|
(this->object_config->brim_type == BrimType::btOuterOnly ||
|
||||||
if (is_outer_wall_first || this->outer_walls_first_for_brim())
|
this->object_config->brim_type == BrimType::btLeadingEdgeOnly) &&
|
||||||
|
this->object_config->brim_width.value > 0))
|
||||||
entities.reverse();
|
entities.reverse();
|
||||||
// Orca: sandwich mode. Apply after 1st layer.
|
// Orca: sandwich mode. Apply after 1st layer.
|
||||||
else if ((this->config->wall_sequence == WallSequence::InnerOuterInner) && layer_id > 0 && !narrow_wall_hole_island){
|
else if ((this->config->wall_sequence == WallSequence::InnerOuterInner) && layer_id > 0){
|
||||||
entities.reverse(); // reverse all entities - order them from external to internal
|
entities.reverse(); // reverse all entities - order them from external to internal
|
||||||
if(entities.entities.size()>2){ // 3 walls minimum needed to do inner outer inner ordering
|
if(entities.entities.size()>2){ // 3 walls minimum needed to do inner outer inner ordering
|
||||||
int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
|
int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
|
||||||
@@ -2427,25 +2412,11 @@ std::vector<PerimeterGeneratorArachneExtrusion> reorderPerimetersByProximity(std
|
|||||||
|
|
||||||
// Function to reorder perimeters starting from a given reference index
|
// Function to reorder perimeters starting from a given reference index
|
||||||
auto reorderFromReference = [&](int referenceIndex) {
|
auto reorderFromReference = [&](int referenceIndex) {
|
||||||
// Find first level touching perimeters. The odd centre line of a wall too narrow for two inner walls is shared
|
std::unordered_set<int> firstLevelIndices;
|
||||||
// by the outer walls on both sides, so those join this island and are printed before it too, holes first.
|
firstLevelIndices.insert(referenceIndex);
|
||||||
std::vector<int> outerIndices{referenceIndex};
|
|
||||||
std::vector<int> firstLevelTouchingIndices;
|
// Find first level touching perimeters
|
||||||
for (size_t next = 0; next < outerIndices.size(); ++next)
|
std::vector<int> firstLevelTouchingIndices = findAllTouchingPerimeters(entities, firstLevelIndices, threshold_external, threshold_internal, 1);
|
||||||
for (int idx : findAllTouchingPerimeters(entities, {outerIndices[next]}, threshold_external, threshold_internal, 1)) {
|
|
||||||
if (std::find(firstLevelTouchingIndices.begin(), firstLevelTouchingIndices.end(), idx) != firstLevelTouchingIndices.end())
|
|
||||||
continue;
|
|
||||||
firstLevelTouchingIndices.push_back(idx);
|
|
||||||
if (!entities[idx].extrusion->is_odd)
|
|
||||||
continue;
|
|
||||||
for (size_t i = 0; i < entities.size(); ++i)
|
|
||||||
if (entities[i].extrusion->inset_idx == 0 && includedIndices.count(int(i)) == 0 &&
|
|
||||||
std::find(outerIndices.begin(), outerIndices.end(), int(i)) == outerIndices.end() &&
|
|
||||||
arachne_lines_touch(*entities[i].extrusion, *entities[idx].extrusion, double(threshold_external)))
|
|
||||||
outerIndices.push_back(int(i));
|
|
||||||
}
|
|
||||||
std::stable_partition(outerIndices.begin(), outerIndices.end(),
|
|
||||||
[&entities](int idx) { return entities[idx].extrusion->is_closed && !entities[idx].is_contour; });
|
|
||||||
// Bring the largest first level perimeter to the front
|
// Bring the largest first level perimeter to the front
|
||||||
// The longest first neighbour is most likely the dominant proximate perimeter
|
// The longest first neighbour is most likely the dominant proximate perimeter
|
||||||
// hence printing it immediately after the external perimeter should speed things up
|
// hence printing it immediately after the external perimeter should speed things up
|
||||||
@@ -2456,10 +2427,8 @@ std::vector<PerimeterGeneratorArachneExtrusion> reorderPerimetersByProximity(std
|
|||||||
std::iter_swap(maxIt, firstLevelTouchingIndices.end() - 1);
|
std::iter_swap(maxIt, firstLevelTouchingIndices.end() - 1);
|
||||||
}
|
}
|
||||||
// Insert first level perimeters into reordered list
|
// Insert first level perimeters into reordered list
|
||||||
for (int idx : outerIndices) {
|
reordered.push_back(entities[referenceIndex]);
|
||||||
reordered.push_back(entities[idx]);
|
includedIndices.insert(referenceIndex);
|
||||||
includedIndices.insert(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int idx : firstLevelTouchingIndices) {
|
for (int idx : firstLevelTouchingIndices) {
|
||||||
if (includedIndices.count(idx) == 0) {
|
if (includedIndices.count(idx) == 0) {
|
||||||
@@ -2788,7 +2757,7 @@ void PerimeterGenerator::process_arachne()
|
|||||||
|
|
||||||
if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
|
if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
|
||||||
is_outer_wall_first =
|
is_outer_wall_first =
|
||||||
this->config->wall_sequence == WallSequence::OuterInner || this->outer_walls_first_for_brim();
|
this->config->wall_sequence == WallSequence::OuterInner;
|
||||||
}
|
}
|
||||||
if (is_outer_wall_first) {
|
if (is_outer_wall_first) {
|
||||||
start_perimeter = 0;
|
start_perimeter = 0;
|
||||||
@@ -3073,16 +3042,6 @@ ExPolygons PerimeterGenerator::printable_slices(const ExPolygons &slices) const
|
|||||||
return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
|
return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PerimeterGenerator::outer_walls_first_for_brim() const
|
|
||||||
{
|
|
||||||
return layer_id == 0 && brim_puts_outer_walls_first(object_config->brim_type.value, object_config->brim_width.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool brim_puts_outer_walls_first(BrimType brim_type, double brim_width)
|
|
||||||
{
|
|
||||||
return brim_width > 0 && (brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btLeadingEdgeOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
||||||
{
|
{
|
||||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
||||||
|
|||||||
@@ -164,8 +164,6 @@ public:
|
|||||||
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
|
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
|
||||||
// ORCA: the slices less the slivers the wall generator prints nothing for, so they never count as support.
|
// ORCA: the slices less the slivers the wall generator prints nothing for, so they never count as support.
|
||||||
ExPolygons printable_slices(const ExPolygons &slices) const;
|
ExPolygons printable_slices(const ExPolygons &slices) const;
|
||||||
// ORCA: whether this layer prints its outer walls first for the brim, see brim_puts_outer_walls_first().
|
|
||||||
bool outer_walls_first_for_brim() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Polygons> generate_lower_polygons_series(float width);
|
std::vector<Polygons> generate_lower_polygons_series(float width);
|
||||||
@@ -185,10 +183,6 @@ private:
|
|||||||
Polygons m_lower_slices_polygons;
|
Polygons m_lower_slices_polygons;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ORCA: a brim against the outer walls makes the first layer print them first, whatever the wall sequence, so the
|
|
||||||
// brim runs straight on into the outer wall. Auto, ear and painted brims may leave the outer walls bare.
|
|
||||||
bool brim_puts_outer_walls_first(BrimType brim_type, double brim_width);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -2293,7 +2293,6 @@ void PresetCollection::load_project_embedded_presets(std::vector<Preset*>& proje
|
|||||||
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file;
|
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
extend_default_config_length(config, false, {});
|
|
||||||
preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||||
//preset->config.apply(std::move(config));
|
//preset->config.apply(std::move(config));
|
||||||
Preset::normalize(preset->config);
|
Preset::normalize(preset->config);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
#include "MutablePolygon.hpp"
|
#include "MutablePolygon.hpp"
|
||||||
#include "PerimeterGenerator.hpp"
|
|
||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "SLA/IndexedMesh.hpp"
|
#include "SLA/IndexedMesh.hpp"
|
||||||
@@ -1465,13 +1464,15 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
|| opt_key == "top_surface_speed") {
|
|| opt_key == "top_surface_speed") {
|
||||||
// Brim is printed below supports, support invalidates brim and skirt.
|
// Brim is printed below supports, support invalidates brim and skirt.
|
||||||
steps.emplace_back(posSupportMaterial);
|
steps.emplace_back(posSupportMaterial);
|
||||||
if (opt_key == "brim_type" || opt_key == "brim_width") {
|
if (opt_key == "brim_type") {
|
||||||
// Regenerate the walls when the brim starts or stops putting the first layer's outer walls first.
|
const auto* old_brim_type = old_config.option<ConfigOptionEnum<BrimType>>(opt_key);
|
||||||
auto outer_walls_first = [](const ConfigOptionResolver &config) {
|
const auto* new_brim_type = new_config.option<ConfigOptionEnum<BrimType>>(opt_key);
|
||||||
return brim_puts_outer_walls_first(config.option<ConfigOptionEnum<BrimType>>("brim_type")->value,
|
//BBS: When switch to manual brim, the object must have brim, then re-generate perimeter
|
||||||
config.option<ConfigOptionFloat>("brim_width")->value);
|
//to make the wall order of first layer to be outer-first
|
||||||
};
|
// btLeadingEdgeOnly is printed as an outer brim (Brim.cpp, BeltBrim.cpp), so it
|
||||||
if (outer_walls_first(old_config) != outer_walls_first(new_config))
|
// takes part in the same first-layer wall order rule.
|
||||||
|
if (old_brim_type->value == btOuterOnly || new_brim_type->value == btOuterOnly ||
|
||||||
|
old_brim_type->value == btLeadingEdgeOnly || new_brim_type->value == btLeadingEdgeOnly)
|
||||||
steps.emplace_back(posPerimeters);
|
steps.emplace_back(posPerimeters);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
|
|||||||
@@ -707,10 +707,8 @@ void tree_supports_generate_paths(
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
if (eec) {
|
if (eec) {
|
||||||
if (!eec->empty()) {
|
std::reverse(eec->entities.begin(), eec->entities.end());
|
||||||
std::reverse(eec->entities.begin(), eec->entities.end());
|
dst.emplace_back(eec.release());
|
||||||
dst.emplace_back(eec.release());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1827,7 +1825,12 @@ void generate_support_toolpaths(
|
|||||||
no_sort = true;
|
no_sort = true;
|
||||||
} else if (support_params.support_style == SupportMaterialStyle::smsTreeOrganic &&
|
} else if (support_params.support_style == SupportMaterialStyle::smsTreeOrganic &&
|
||||||
(config.support_base_pattern == smpNone || config.support_base_pattern == smpDefault)) {
|
(config.support_base_pattern == smpNone || config.support_base_pattern == smpDefault)) {
|
||||||
tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params);
|
// Orca: A special case for the hollow Organic supports
|
||||||
|
// Orca: If the tree supports are too tall, use a double wall to make it stronger
|
||||||
|
SupportParameters support_params2 = support_params;
|
||||||
|
if (support_layer.print_z > 100.0)
|
||||||
|
support_params2.tree_branch_diameter_double_wall_area_scaled = 0.1;
|
||||||
|
tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow, support_params2);
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
if (! done)
|
if (! done)
|
||||||
|
|||||||
@@ -2149,9 +2149,7 @@ void TreeSupport::draw_circles()
|
|||||||
const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning;
|
const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning;
|
||||||
coordf_t support_extrusion_width = m_support_params.support_extrusion_width;
|
coordf_t support_extrusion_width = m_support_params.support_extrusion_width;
|
||||||
const float tree_brim_width = config.tree_support_brim_width.value;
|
const float tree_brim_width = config.tree_support_brim_width.value;
|
||||||
// Reinforce branches moving more than half a support-line width between layers.
|
|
||||||
// Square the threshold for comparison with the squared XY movement below.
|
|
||||||
const double max_move_sq = sqr(double(scale_(m_object_config->support_line_width / 2)));
|
|
||||||
// Belt floor: the first object layer is not on a flat bed — it rests on the
|
// Belt floor: the first object layer is not on a flat bed — it rests on the
|
||||||
// tilted, moving belt. So the first-object-layer adhesion features (the tree
|
// tilted, moving belt. So the first-object-layer adhesion features (the tree
|
||||||
// support brim, the hybrid first-layer base expansion) must be suppressed:
|
// support brim, the hybrid first-layer base expansion) must be suppressed:
|
||||||
@@ -2203,7 +2201,7 @@ void TreeSupport::draw_circles()
|
|||||||
size_t first_base_roof_area = 0;
|
size_t first_base_roof_area = 0;
|
||||||
bool floor_interface_as_base = false;
|
bool floor_interface_as_base = false;
|
||||||
bool has_circle_node = false;
|
bool has_circle_node = false;
|
||||||
ExPolygons extra_wall_areas;
|
bool need_extra_wall = false;
|
||||||
ExPolygons collision_sharp_tails;
|
ExPolygons collision_sharp_tails;
|
||||||
ExPolygons collision_base;
|
ExPolygons collision_base;
|
||||||
auto get_collision = [&](bool sharp_tail) -> ExPolygons &{
|
auto get_collision = [&](bool sharp_tail) -> ExPolygons &{
|
||||||
@@ -2281,6 +2279,7 @@ void TreeSupport::draw_circles()
|
|||||||
// area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0));
|
// area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0));
|
||||||
|
|
||||||
if (!area.empty()) has_circle_node = true;
|
if (!area.empty()) has_circle_node = true;
|
||||||
|
if (node.need_extra_wall) need_extra_wall = true;
|
||||||
|
|
||||||
// merge overhang to get a smoother interface surface
|
// merge overhang to get a smoother interface surface
|
||||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
||||||
@@ -2295,10 +2294,6 @@ void TreeSupport::draw_circles()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the Pythagorean theorem to compare movement magnitude (dx² + dy²) without a square root.
|
|
||||||
if (node.is_processed && node.movement.cast<double>().squaredNorm() > max_move_sq)
|
|
||||||
append(extra_wall_areas, area);
|
|
||||||
|
|
||||||
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
||||||
append(roof_gap_areas, area);
|
append(roof_gap_areas, area);
|
||||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
||||||
@@ -2535,8 +2530,7 @@ void TreeSupport::draw_circles()
|
|||||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||||
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
|
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
|
||||||
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
||||||
area_groups.back().need_extra_wall = !area_groups.back().need_infill &&
|
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
||||||
!extra_wall_areas.empty() && overlaps({expoly}, extra_wall_areas);
|
|
||||||
}
|
}
|
||||||
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
||||||
auto &expoly = ts_layer->roof_areas[roof_idx];
|
auto &expoly = ts_layer->roof_areas[roof_idx];
|
||||||
@@ -3439,6 +3433,11 @@ void TreeSupport::smooth_nodes()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float max_move = scale_(m_object_config->support_line_width / 2);
|
||||||
|
// if the branch is very tall, the tip also needs extra wall
|
||||||
|
float thresh_tall_branch = 100;
|
||||||
|
float thresh_dist_to_top = 30;
|
||||||
|
|
||||||
for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) {
|
for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) {
|
||||||
std::vector<SupportNode *> &curr_layer_nodes = contact_nodes[layer_nr];
|
std::vector<SupportNode *> &curr_layer_nodes = contact_nodes[layer_nr];
|
||||||
if (curr_layer_nodes.empty()) continue;
|
if (curr_layer_nodes.empty()) continue;
|
||||||
@@ -3448,17 +3447,20 @@ void TreeSupport::smooth_nodes()
|
|||||||
std::vector<double> radii;
|
std::vector<double> radii;
|
||||||
std::vector<SupportNode *> branch;
|
std::vector<SupportNode *> branch;
|
||||||
SupportNode * p_node = node;
|
SupportNode * p_node = node;
|
||||||
|
float total_height = 0;
|
||||||
// add a fixed head if it's not a polygon node, see STUDIO-4403
|
// add a fixed head if it's not a polygon node, see STUDIO-4403
|
||||||
// Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling
|
// Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling
|
||||||
if (node->child && node->child->type!=ePolygon) {
|
if (node->child && node->child->type!=ePolygon) {
|
||||||
pts.push_back(p_node->child->position);
|
pts.push_back(p_node->child->position);
|
||||||
radii.push_back(p_node->child->radius);
|
radii.push_back(p_node->child->radius);
|
||||||
branch.push_back(p_node->child);
|
branch.push_back(p_node->child);
|
||||||
|
total_height += p_node->child->height;
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
pts.push_back(p_node->position);
|
pts.push_back(p_node->position);
|
||||||
radii.push_back(p_node->radius);
|
radii.push_back(p_node->radius);
|
||||||
branch.push_back(p_node);
|
branch.push_back(p_node);
|
||||||
|
total_height += p_node->height;
|
||||||
p_node = p_node->parent;
|
p_node = p_node->parent;
|
||||||
} while (p_node && !p_node->is_processed);
|
} while (p_node && !p_node->is_processed);
|
||||||
if (pts.size() < 3) continue;
|
if (pts.size() < 3) continue;
|
||||||
@@ -3477,6 +3479,9 @@ void TreeSupport::smooth_nodes()
|
|||||||
branch[i]->radius = radii1[i];
|
branch[i]->radius = radii1[i];
|
||||||
branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2;
|
branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2;
|
||||||
branch[i]->is_processed = true;
|
branch[i]->is_processed = true;
|
||||||
|
if (branch[i]->parents.size() > 1 || (branch[i]->movement.x() > max_move || branch[i]->movement.y() > max_move) ||
|
||||||
|
(total_height > thresh_tall_branch && branch[i]->dist_mm_to_top < thresh_dist_to_top))
|
||||||
|
branch[i]->need_extra_wall = true;
|
||||||
BOOST_LOG_TRIVIAL(info) << "smooth_nodes: layer_nr=" << layer_nr << ", i=" << i << ", pt=" << pt << ", movement=" << branch[i]->movement << ", radius=" << branch[i]->radius;
|
BOOST_LOG_TRIVIAL(info) << "smooth_nodes: layer_nr=" << layer_nr << ", i=" << i << ", pt=" << pt << ", movement=" << branch[i]->movement << ", radius=" << branch[i]->radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3484,6 +3489,13 @@ void TreeSupport::smooth_nodes()
|
|||||||
std::swap(pts, pts1);
|
std::swap(pts, pts1);
|
||||||
std::swap(radii, radii1);
|
std::swap(radii, radii1);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// interpolate need_extra_wall in the end
|
||||||
|
for (size_t i = 1; i < branch.size() - 1; i++) {
|
||||||
|
if (branch[i - 1]->need_extra_wall && branch[i + 1]->need_extra_wall)
|
||||||
|
branch[i]->need_extra_wall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ struct SupportNode
|
|||||||
TreeNodeType type = eCircle;
|
TreeNodeType type = eCircle;
|
||||||
bool is_corner = false;
|
bool is_corner = false;
|
||||||
bool is_processed = false;
|
bool is_processed = false;
|
||||||
|
bool need_extra_wall = false;
|
||||||
bool is_sharp_tail = false;
|
bool is_sharp_tail = false;
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area
|
ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ std::vector<float> Layers::get_zs() const
|
|||||||
|
|
||||||
size_t Layers::get_layer_id_at(float z) const
|
size_t Layers::get_layer_id_at(float z) const
|
||||||
{
|
{
|
||||||
auto iter = std::upper_bound(m_items.begin(), m_items.end(), z, [](float z, const Item& item) { return item.z > z; });
|
auto iter = std::upper_bound(m_items.begin(), m_items.end(), z, [](float z, const Item& item) { return item.z < z; });
|
||||||
return std::distance(m_items.begin(), iter);
|
return std::distance(m_items.begin(), iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21178,12 +21178,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
|
|||||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||||
if (!dev) return;
|
if (!dev) return;
|
||||||
|
|
||||||
const std::string dev_id = evt.GetString().ToStdString();
|
dev->set_selected_machine(evt.GetString().ToStdString());
|
||||||
MachineObject* selected = dev->get_selected_machine();
|
|
||||||
// After a LAN print, the completion event reselects the same printer.
|
|
||||||
// Keep the active session while asynchronous status traffic may still be in flight.
|
|
||||||
if (!selected || selected->get_dev_id() != dev_id || selected->connection_type() != "lan")
|
|
||||||
dev->set_selected_machine(dev_id);
|
|
||||||
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||||
// Selects the status page on a built Device tab; one built by the switch starts there.
|
// Selects the status page on a built Device tab; one built by the switch starts there.
|
||||||
MonitorPanel* curr_monitor = MonitorPanel::if_built();
|
MonitorPanel* curr_monitor = MonitorPanel::if_built();
|
||||||
|
|||||||
@@ -4087,7 +4087,7 @@ void SelectMachineDialog::update_user_printer()
|
|||||||
{
|
{
|
||||||
m_printer_last_select = "";
|
m_printer_last_select = "";
|
||||||
update_select_layout(nullptr);
|
update_select_layout(nullptr);
|
||||||
m_printer_box->GetPrinterComboBox()->SetValue(_L("No available printers"));
|
m_printer_box->GetPrinterComboBox()->SetTextLabel("");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
||||||
|
|||||||
@@ -474,14 +474,17 @@ void Selection::add_curr_plate()
|
|||||||
if (!m_valid)
|
if (!m_valid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_plate(wxGetApp().plater()->get_partplate_list().get_curr_plate_index());
|
|
||||||
if (volume_idxs.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
wxGetApp().plater()->take_snapshot(std::string("Selection-Add Curr Plate All!"));
|
wxGetApp().plater()->take_snapshot(std::string("Selection-Add Curr Plate All!"));
|
||||||
m_mode = Instance;
|
m_mode = Instance;
|
||||||
clear();
|
clear();
|
||||||
do_add_volumes(volume_idxs);
|
|
||||||
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||||
|
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
||||||
|
if (plate && plate->contain_instance_totally(obj_idx, 0)) {
|
||||||
|
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_object(obj_idx);
|
||||||
|
do_add_volumes(volume_idxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update_type();
|
update_type();
|
||||||
this->set_bounding_boxes_dirty();
|
this->set_bounding_boxes_dirty();
|
||||||
@@ -508,14 +511,20 @@ void Selection::remove_curr_plate()
|
|||||||
if (!m_valid)
|
if (!m_valid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_plate(wxGetApp().plater()->get_partplate_list().get_curr_plate_index());
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||||
if (volume_idxs.empty())
|
if (plate->empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxGetApp().plater()->take_snapshot(std::string("Selection-Delete Curr Plate All"));
|
wxGetApp().plater()->take_snapshot(std::string("Selection-Delete Curr Plate All"));
|
||||||
m_mode = Instance;
|
m_mode = Instance;
|
||||||
clear();
|
clear();
|
||||||
do_add_volumes(volume_idxs);
|
|
||||||
|
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
|
||||||
|
if (plate && plate->contain_instance(obj_idx, 0)) {
|
||||||
|
std::vector<unsigned int> volume_idxs = get_volume_idxs_from_object(obj_idx);
|
||||||
|
do_add_volumes(volume_idxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update_type();
|
update_type();
|
||||||
this->set_bounding_boxes_dirty();
|
this->set_bounding_boxes_dirty();
|
||||||
@@ -2261,27 +2270,6 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_volume(unsigned int ob
|
|||||||
return idxs;
|
return idxs;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned int> Selection::get_volume_idxs_from_plate(const int plate_idx) const
|
|
||||||
{
|
|
||||||
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx);
|
|
||||||
std::vector<unsigned int> idxs;
|
|
||||||
if (!plate)
|
|
||||||
return idxs;
|
|
||||||
|
|
||||||
for (int obj_idx = 0; obj_idx < (int)m_model->objects.size(); obj_idx++) {
|
|
||||||
const ModelObject* obj = m_model->objects[obj_idx];
|
|
||||||
for (int inst_idx = 0; inst_idx < (int)obj->instances.size(); inst_idx++) {
|
|
||||||
// on this plate or overlapping it in some way (fully inside, oversized or partly outside)
|
|
||||||
if (plate->contain_instance(obj_idx, inst_idx) || plate->intersect_instance(obj_idx, inst_idx)) {
|
|
||||||
std::vector<unsigned int> inst_idxs = get_volume_idxs_from_instance(obj_idx, inst_idx);
|
|
||||||
idxs.insert(idxs.end(), inst_idxs.begin(), inst_idxs.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return idxs;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<unsigned int> Selection::get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const
|
std::vector<unsigned int> Selection::get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const
|
||||||
{
|
{
|
||||||
std::vector<unsigned int> idxs;
|
std::vector<unsigned int> idxs;
|
||||||
|
|||||||
@@ -409,8 +409,6 @@ public:
|
|||||||
std::vector<unsigned int> get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const;
|
std::vector<unsigned int> get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const;
|
||||||
// returns the idx of the volume corresponding to the volume with the given idxs
|
// returns the idx of the volume corresponding to the volume with the given idxs
|
||||||
std::vector<unsigned int> get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const;
|
std::vector<unsigned int> get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const;
|
||||||
// returns the list of idxs of the volumes of all instances on the given plate or overlapping it. (fully inside, oversized or partly outside)
|
|
||||||
std::vector<unsigned int> get_volume_idxs_from_plate(const int plate_idx) const;
|
|
||||||
// returns the list of idxs of the volumes contained in the selection but not in the given list
|
// returns the list of idxs of the volumes contained in the selection but not in the given list
|
||||||
std::vector<unsigned int> get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const;
|
std::vector<unsigned int> get_missing_volume_idxs_from(const std::vector<unsigned int>& volume_idxs) const;
|
||||||
// returns the list of idxs of the volumes contained in the given list but not in the selection
|
// returns the list of idxs of the volumes contained in the given list but not in the selection
|
||||||
|
|||||||
@@ -1229,7 +1229,7 @@ void SendToPrinterDialog::update_user_printer()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_printer_last_select = "";
|
m_printer_last_select = "";
|
||||||
m_comboBox_printer->SetValue(_L("No available printers"));
|
m_comboBox_printer->SetTextLabel("");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << m_printer_last_select << std::endl;
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ void ComboBox::DoDeleteOneItem(unsigned int pos)
|
|||||||
|
|
||||||
void ComboBox::ForceDropdownOpen()
|
void ComboBox::ForceDropdownOpen()
|
||||||
{
|
{
|
||||||
if (!IsEnabled() || items.empty())
|
if (!IsEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!drop_down) {
|
if (!drop_down) {
|
||||||
@@ -398,7 +398,7 @@ void ComboBox::mouseDown(wxMouseEvent &event)
|
|||||||
SetFocus();
|
SetFocus();
|
||||||
if (drop_down) {
|
if (drop_down) {
|
||||||
drop.Hide();
|
drop.Hide();
|
||||||
} else if (!items.empty() && drop.HasDismissLongTime()) { // DropDown paints nothing for an empty list
|
} else if (drop.HasDismissLongTime()) {
|
||||||
drop.autoPosition();
|
drop.autoPosition();
|
||||||
drop_down = true;
|
drop_down = true;
|
||||||
drop.Popup(&drop);
|
drop.Popup(&drop);
|
||||||
@@ -426,7 +426,7 @@ void ComboBox::keyDown(wxKeyEvent& event)
|
|||||||
case WXK_SPACE:
|
case WXK_SPACE:
|
||||||
if (drop_down) {
|
if (drop_down) {
|
||||||
drop.DismissAndNotify();
|
drop.DismissAndNotify();
|
||||||
} else if (!items.empty() && drop.HasDismissLongTime()) {
|
} else if (drop.HasDismissLongTime()) {
|
||||||
drop.autoPosition();
|
drop.autoPosition();
|
||||||
drop_down = true;
|
drop_down = true;
|
||||||
drop.Popup();
|
drop.Popup();
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Point.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
|
||||||
#include "libslic3r/Polyline.hpp"
|
#include "libslic3r/Polyline.hpp"
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -995,384 +994,3 @@ TEST_CASE("A hole whose outer wall touches the contour's runs with it", "[Perime
|
|||||||
CHECK(layers[i].holes_ccw.front() == ccw);
|
CHECK(layers[i].holes_ccw.front() == ccw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// The outer radius of round_tube().
|
|
||||||
const double tube_radius = 11.5;
|
|
||||||
|
|
||||||
// Every setting the tube walls below are measured against. With 0.5mm walls a 1mm tube wall holds only the two
|
|
||||||
// outer walls, touching, and every further 0.5mm makes room for one more inner wall.
|
|
||||||
DynamicPrintConfig tube_config(const char *wall_generator, int wall_loops)
|
|
||||||
{
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({
|
|
||||||
{ "wall_generator", wall_generator },
|
|
||||||
{ "wall_loops", wall_loops },
|
|
||||||
{ "wall_sequence", "inner wall/outer wall" },
|
|
||||||
{ "wall_direction", "ccw" },
|
|
||||||
{ "layer_height", 0.2 },
|
|
||||||
{ "initial_layer_print_height", 0.2 },
|
|
||||||
{ "nozzle_diameter", "0.4" },
|
|
||||||
{ "outer_wall_line_width", 0.5 },
|
|
||||||
{ "inner_wall_line_width", 0.5 },
|
|
||||||
{ "precise_outer_wall", true },
|
|
||||||
{ "detect_thin_wall", false },
|
|
||||||
{ "only_one_wall_top", false },
|
|
||||||
{ "only_one_wall_first_layer", false },
|
|
||||||
{ "brim_type", "no_brim" },
|
|
||||||
{ "detect_overhang_wall", true },
|
|
||||||
{ "overhang_reverse", false },
|
|
||||||
{ "overhang_reverse_threshold", "0%" },
|
|
||||||
});
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A 2mm tall round tube whose wall is `wall` mm thick.
|
|
||||||
Print &round_tube(Print &print, Model &model, const DynamicPrintConfig &config, double wall)
|
|
||||||
{
|
|
||||||
ModelObject *object = model.add_object();
|
|
||||||
object->name = "round_tube.stl";
|
|
||||||
object->add_volume(make_cylinder(tube_radius, 2.), ModelVolumeType::MODEL_PART, false);
|
|
||||||
object->add_volume(make_cylinder(tube_radius - wall, 2.), ModelVolumeType::NEGATIVE_VOLUME, false);
|
|
||||||
object->add_instance();
|
|
||||||
object->ensure_on_bed();
|
|
||||||
|
|
||||||
print.auto_assign_extruders(object);
|
|
||||||
print.apply(model, config);
|
|
||||||
print.validate();
|
|
||||||
print.set_status_silent();
|
|
||||||
print.process();
|
|
||||||
return print;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The side of the tube wall a wall loop lies on. The centre is the middle of the tube wall, where Arachne lays the
|
|
||||||
// inner wall both sides share when the tube wall is too narrow for a pair of them.
|
|
||||||
enum class TubeSide { Contour, Hole, Centre };
|
|
||||||
|
|
||||||
struct TubeLoop {
|
|
||||||
TubeSide side;
|
|
||||||
int inset;
|
|
||||||
bool ccw;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The wall loops of every layer of round_tube() in extrusion order.
|
|
||||||
std::vector<std::vector<TubeLoop>> tube_loops(const Print &print, double wall)
|
|
||||||
{
|
|
||||||
const double centre = tube_radius - 0.5 * wall;
|
|
||||||
std::vector<std::vector<TubeLoop>> out;
|
|
||||||
for (const Layer *layer : print.objects().front()->layers()) {
|
|
||||||
const Vec2d axis = get_extents(layer->lslices).center().cast<double>();
|
|
||||||
std::vector<TubeLoop> &loops = out.emplace_back();
|
|
||||||
for (const LayerRegion *region : layer->regions())
|
|
||||||
for (const ExtrusionEntity *entity : region->perimeters.flatten().entities) {
|
|
||||||
if (! entity->is_loop())
|
|
||||||
continue;
|
|
||||||
const ExtrusionLoop *loop = static_cast<const ExtrusionLoop*>(entity);
|
|
||||||
const Polygon polygon = loop->polygon();
|
|
||||||
double radius = 0.;
|
|
||||||
for (const Point &p : polygon.points)
|
|
||||||
radius += unscaled<double>((p.cast<double>() - axis).norm());
|
|
||||||
radius /= double(polygon.points.size());
|
|
||||||
const TubeSide side = radius > centre + 0.1 ? TubeSide::Contour : radius < centre - 0.1 ? TubeSide::Hole : TubeSide::Centre;
|
|
||||||
loops.push_back({ side, loop->inset_idx, polygon.is_counter_clockwise() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The insets one side of a wall is printed in, given its deepest inset: inside out for Inner/Outer, outside in for
|
|
||||||
// Outer/Inner, and for Inner/Outer/Inner the third wall onwards inside out, then the outer wall, then the first inner
|
|
||||||
// wall. Inner/Outer/Inner prints its first layer Inner/Outer, and outside in when there is no third wall.
|
|
||||||
std::vector<int> wall_sequence_insets(const std::string &wall_sequence, bool first_layer, int deepest)
|
|
||||||
{
|
|
||||||
std::vector<int> insets;
|
|
||||||
if (wall_sequence == "inner wall/outer wall" || (wall_sequence == "inner-outer-inner wall" && first_layer)) {
|
|
||||||
for (int inset = deepest; inset >= 0; -- inset)
|
|
||||||
insets.push_back(inset);
|
|
||||||
} else if (wall_sequence == "outer wall/inner wall" || deepest < 2) {
|
|
||||||
for (int inset = 0; inset <= deepest; ++ inset)
|
|
||||||
insets.push_back(inset);
|
|
||||||
} else {
|
|
||||||
for (int inset = deepest; inset >= 2; -- inset)
|
|
||||||
insets.push_back(inset);
|
|
||||||
insets.push_back(0);
|
|
||||||
insets.push_back(1);
|
|
||||||
}
|
|
||||||
return insets;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether a wall of round_tube() runs with the contour: all but the walls of the hole, and those too when the hole
|
|
||||||
// is a thin wall hole, its outer wall touching the contour's with nothing between them.
|
|
||||||
bool runs_with_contour(const TubeLoop &loop, bool thin_wall_hole)
|
|
||||||
{
|
|
||||||
return loop.side != TubeSide::Hole || thin_wall_hole;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The insets of the walls on one side of a round_tube() layer, the shared centre line included, in extrusion order.
|
|
||||||
std::vector<int> side_insets(const std::vector<TubeLoop> &loops, TubeSide side)
|
|
||||||
{
|
|
||||||
std::vector<int> insets;
|
|
||||||
for (const TubeLoop &loop : loops)
|
|
||||||
if (loop.side == side || loop.side == TubeSide::Centre)
|
|
||||||
insets.push_back(loop.inset);
|
|
||||||
return insets;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The walls of a round_tube() layer in extrusion order, as side and inset: "c0 c1 h0 h1" is the contour's outer wall,
|
|
||||||
// then its first inner wall, then the same for the hole, and "m" marks the wall in the middle both sides share.
|
|
||||||
std::string wall_order(const std::vector<TubeLoop> &loops)
|
|
||||||
{
|
|
||||||
std::string out;
|
|
||||||
for (const TubeLoop &loop : loops)
|
|
||||||
out += std::string(out.empty() ? "" : " ") + (loop.side == TubeSide::Contour ? "c" : loop.side == TubeSide::Hole ? "h" : "m") +
|
|
||||||
std::to_string(loop.inset);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// Each side of a tube wall prints its walls in the order the wall sequence asks for, from two walls a side up to
|
|
||||||
// four. The inner wall Arachne lays in the middle of a wall too narrow for a pair of them is an inner wall of both
|
|
||||||
// sides; Classic fills that middle with gap fill instead, so it has one wall less a side there.
|
|
||||||
TEST_CASE("Each side of a tube wall prints its walls in the wall sequence order", "[Perimeters]")
|
|
||||||
{
|
|
||||||
struct TubeWall {
|
|
||||||
double wall;
|
|
||||||
int wall_loops;
|
|
||||||
// Walls a side, a shared inner wall included.
|
|
||||||
int arachne_walls;
|
|
||||||
int classic_walls;
|
|
||||||
};
|
|
||||||
const TubeWall tube = GENERATE(values<TubeWall>({
|
|
||||||
{ 1.5, 3, 2, 1 }, { 2.0, 2, 2, 2 }, { 2.5, 3, 3, 2 }, { 3.0, 3, 3, 3 }, { 4.0, 4, 4, 4 } }));
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
const std::string wall_sequence = GENERATE("inner wall/outer wall", "outer wall/inner wall", "inner-outer-inner wall");
|
|
||||||
CAPTURE(tube.wall, tube.wall_loops, wall_generator, wall_sequence);
|
|
||||||
|
|
||||||
DynamicPrintConfig config = tube_config(wall_generator, tube.wall_loops);
|
|
||||||
config.set_deserialize_strict("wall_sequence", wall_sequence);
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, config, tube.wall);
|
|
||||||
const std::vector<std::vector<TubeLoop>> layers = tube_loops(print, tube.wall);
|
|
||||||
// 2mm at 0.2mm layers.
|
|
||||||
REQUIRE(layers.size() == 10);
|
|
||||||
|
|
||||||
const int walls = std::string(wall_generator) == "arachne" ? tube.arachne_walls : tube.classic_walls;
|
|
||||||
for (size_t i = 0; i < layers.size(); ++ i) {
|
|
||||||
for (const TubeSide side : { TubeSide::Contour, TubeSide::Hole }) {
|
|
||||||
const bool contour_side = side == TubeSide::Contour;
|
|
||||||
CAPTURE(i, contour_side);
|
|
||||||
const std::vector<int> insets = side_insets(layers[i], side);
|
|
||||||
REQUIRE(int(insets.size()) == walls);
|
|
||||||
CHECK(insets == wall_sequence_insets(wall_sequence, i == 0, walls - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The outer wall of a thin wall hole touches the contour's with nothing between them. The hole is printed first, so
|
|
||||||
// the contour leans on it, unless the wall sequence puts the outer walls first.
|
|
||||||
TEST_CASE("A thin wall hole is printed before the contour unless the outer walls go first", "[Perimeters]")
|
|
||||||
{
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
const std::string wall_sequence = GENERATE("inner wall/outer wall", "outer wall/inner wall", "inner-outer-inner wall");
|
|
||||||
CAPTURE(wall_generator, wall_sequence);
|
|
||||||
|
|
||||||
DynamicPrintConfig config = tube_config(wall_generator, 3);
|
|
||||||
config.set_deserialize_strict("wall_sequence", wall_sequence);
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, config, 1.);
|
|
||||||
const std::vector<std::vector<TubeLoop>> layers = tube_loops(print, 1.);
|
|
||||||
REQUIRE(layers.size() == 10);
|
|
||||||
|
|
||||||
const TubeSide first = wall_sequence == "outer wall/inner wall" ? TubeSide::Contour : TubeSide::Hole;
|
|
||||||
for (size_t i = 0; i < layers.size(); ++ i) {
|
|
||||||
CAPTURE(i);
|
|
||||||
REQUIRE(layers[i].size() == 2);
|
|
||||||
REQUIRE(layers[i][0].side != layers[i][1].side);
|
|
||||||
CHECK(layers[i].front().side == first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A 1.5mm tube wall leaves Inner/Outer/Inner no third wall to sandwich, only the inner wall Arachne lays in the middle
|
|
||||||
// for both sides, or Classic's gap fill. Past the first layer it prints the hole first, then the contour, then the
|
|
||||||
// middle.
|
|
||||||
TEST_CASE("Inner/Outer/Inner prints the hole of a narrow tube wall first and the middle last", "[Perimeters]")
|
|
||||||
{
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
CAPTURE(wall_generator);
|
|
||||||
|
|
||||||
DynamicPrintConfig config = tube_config(wall_generator, 3);
|
|
||||||
config.set_deserialize_strict("wall_sequence", "inner-outer-inner wall");
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, config, 1.5);
|
|
||||||
const std::vector<std::vector<TubeLoop>> layers = tube_loops(print, 1.5);
|
|
||||||
REQUIRE(layers.size() == 10);
|
|
||||||
|
|
||||||
// Classic's gap fill is not a wall loop, and goes after the walls.
|
|
||||||
const std::string expected = std::string(wall_generator) == "arachne" ? "h0 c0 m1" : "h0 c0";
|
|
||||||
for (size_t i = 1; i < layers.size(); ++ i) {
|
|
||||||
CAPTURE(i);
|
|
||||||
CHECK(wall_order(layers[i]) == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The contour's walls run in the wall direction and the hole's against it, except that the walls of a thin wall
|
|
||||||
// hole run with the contour, as does the inner wall Arachne shares between both sides of a 1.5mm tube wall.
|
|
||||||
TEST_CASE("Tube walls run in the wall direction and hole walls against it", "[Perimeters]")
|
|
||||||
{
|
|
||||||
const double wall = GENERATE(1.0, 1.5, 2.0, 3.0);
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
const std::string wall_direction = GENERATE("ccw", "cw");
|
|
||||||
CAPTURE(wall, wall_generator, wall_direction);
|
|
||||||
|
|
||||||
DynamicPrintConfig config = tube_config(wall_generator, 3);
|
|
||||||
config.set_deserialize_strict("wall_direction", wall_direction);
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, config, wall);
|
|
||||||
const std::vector<std::vector<TubeLoop>> layers = tube_loops(print, wall);
|
|
||||||
REQUIRE(layers.size() == 10);
|
|
||||||
|
|
||||||
const bool thin_wall_hole = wall == 1.;
|
|
||||||
for (size_t i = 0; i < layers.size(); ++ i) {
|
|
||||||
CAPTURE(i);
|
|
||||||
REQUIRE(std::any_of(layers[i].begin(), layers[i].end(), [](const TubeLoop &loop) { return loop.side == TubeSide::Contour; }));
|
|
||||||
REQUIRE(std::any_of(layers[i].begin(), layers[i].end(), [](const TubeLoop &loop) { return loop.side == TubeSide::Hole; }));
|
|
||||||
for (const TubeLoop &loop : layers[i]) {
|
|
||||||
CAPTURE(int(loop.side), loop.inset);
|
|
||||||
CHECK(loop.ccw == (runs_with_contour(loop, thin_wall_hole) == (wall_direction == "ccw")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse on even with no threshold reverses every wall of every other layer, or only the inner walls when it is
|
|
||||||
// restricted to them, and leaves the order of the walls alone.
|
|
||||||
TEST_CASE("Reverse on even reverses the tube walls of every other layer", "[Perimeters]")
|
|
||||||
{
|
|
||||||
const double wall = GENERATE(1.0, 1.5, 3.0);
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
const bool reverse_internal_only = GENERATE(false, true);
|
|
||||||
CAPTURE(wall, wall_generator, reverse_internal_only);
|
|
||||||
|
|
||||||
auto slice = [&](bool overhang_reverse) {
|
|
||||||
DynamicPrintConfig config = tube_config(wall_generator, 3);
|
|
||||||
config.set_deserialize_strict({
|
|
||||||
{ "overhang_reverse", overhang_reverse },
|
|
||||||
{ "overhang_reverse_internal_only", reverse_internal_only },
|
|
||||||
});
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, config, wall);
|
|
||||||
return tube_loops(print, wall);
|
|
||||||
};
|
|
||||||
const std::vector<std::vector<TubeLoop>> plain = slice(false);
|
|
||||||
const std::vector<std::vector<TubeLoop>> reversed = slice(true);
|
|
||||||
REQUIRE(plain.size() == 10);
|
|
||||||
REQUIRE(reversed.size() == plain.size());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < plain.size(); ++ i) {
|
|
||||||
CAPTURE(i);
|
|
||||||
REQUIRE(reversed[i].size() == plain[i].size());
|
|
||||||
for (size_t j = 0; j < plain[i].size(); ++ j) {
|
|
||||||
CAPTURE(j);
|
|
||||||
CHECK(reversed[i][j].side == plain[i][j].side);
|
|
||||||
CHECK(reversed[i][j].inset == plain[i][j].inset);
|
|
||||||
const bool flips = i % 2 == 1 && (! reverse_internal_only || plain[i][j].inset > 0);
|
|
||||||
CHECK(reversed[i][j].ccw == (plain[i][j].ccw != flips));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
DynamicPrintConfig brim_tube_config(const char *wall_generator, const std::string &wall_sequence, const char *brim_type, double brim_width)
|
|
||||||
{
|
|
||||||
DynamicPrintConfig config = tube_config(wall_generator, 3);
|
|
||||||
config.set_deserialize_strict({
|
|
||||||
{ "wall_sequence", wall_sequence },
|
|
||||||
{ "brim_type", brim_type },
|
|
||||||
{ "brim_width", brim_width },
|
|
||||||
});
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// A brim against the outer walls makes the first layer print them first, whatever the wall sequence, so the brim runs
|
|
||||||
// straight on into the outer wall: each side of the tube wall outside in, and the contour before a thin wall hole.
|
|
||||||
// Brims that may leave the outer walls bare keep the wall sequence, and so do the layers above.
|
|
||||||
TEST_CASE("A brim against the outer walls makes the first layer print them first", "[Perimeters]")
|
|
||||||
{
|
|
||||||
struct Brim {
|
|
||||||
const char *type;
|
|
||||||
double width;
|
|
||||||
bool outer_walls_first;
|
|
||||||
};
|
|
||||||
const Brim brim = GENERATE(values<Brim>({
|
|
||||||
{ "no_brim", 5., false }, { "inner_only", 5., false }, { "auto_brim", 5., false }, { "outer_only", 0., false },
|
|
||||||
{ "outer_only", 5., true }, { "outer_and_inner", 5., true }, { "leading_edge_only", 5., true } }));
|
|
||||||
const double wall = GENERATE(1.0, 3.0);
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
const std::string wall_sequence = GENERATE("inner wall/outer wall", "inner-outer-inner wall");
|
|
||||||
CAPTURE(brim.type, brim.width, wall, wall_generator, wall_sequence);
|
|
||||||
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, brim_tube_config(wall_generator, wall_sequence, brim.type, brim.width), wall);
|
|
||||||
const std::vector<std::vector<TubeLoop>> layers = tube_loops(print, wall);
|
|
||||||
REQUIRE(layers.size() == 10);
|
|
||||||
|
|
||||||
// The thin wall hole has its outer wall a side, the 3mm tube wall three walls a side.
|
|
||||||
const size_t walls = wall == 1. ? 1 : 3;
|
|
||||||
for (const size_t i : { size_t(0), size_t(1) }) {
|
|
||||||
const bool outer_walls_first = brim.outer_walls_first && i == 0;
|
|
||||||
for (const TubeSide side : { TubeSide::Contour, TubeSide::Hole }) {
|
|
||||||
const bool contour_side = side == TubeSide::Contour;
|
|
||||||
CAPTURE(i, contour_side);
|
|
||||||
const std::vector<int> insets = side_insets(layers[i], side);
|
|
||||||
REQUIRE(insets.size() == walls);
|
|
||||||
CHECK(insets == wall_sequence_insets(outer_walls_first ? "outer wall/inner wall" : wall_sequence, i == 0, int(walls) - 1));
|
|
||||||
}
|
|
||||||
if (wall == 1.)
|
|
||||||
CHECK(layers[i].front().side == (outer_walls_first ? TubeSide::Contour : TubeSide::Hole));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changing the brim of a sliced print regenerates the walls when the first layer's wall order changes with it, so
|
|
||||||
// the print gets the walls a slice from scratch with the new brim gets.
|
|
||||||
TEST_CASE("Changing the brim updates the wall order of the first layer", "[Perimeters]")
|
|
||||||
{
|
|
||||||
struct BrimChange {
|
|
||||||
const char *from_type;
|
|
||||||
double from_width;
|
|
||||||
const char *to_type;
|
|
||||||
double to_width;
|
|
||||||
};
|
|
||||||
const BrimChange change = GENERATE(values<BrimChange>({
|
|
||||||
{ "outer_only", 0., "outer_only", 5. }, { "outer_only", 5., "outer_only", 0. },
|
|
||||||
{ "no_brim", 5., "outer_and_inner", 5. }, { "outer_and_inner", 5., "inner_only", 5. } }));
|
|
||||||
const char *wall_generator = GENERATE("classic", "arachne");
|
|
||||||
CAPTURE(change.from_type, change.from_width, change.to_type, change.to_width, wall_generator);
|
|
||||||
|
|
||||||
const std::string wall_sequence = "inner wall/outer wall";
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
round_tube(print, model, brim_tube_config(wall_generator, wall_sequence, change.from_type, change.from_width), 3.);
|
|
||||||
const std::string before = wall_order(tube_loops(print, 3.).front());
|
|
||||||
print.apply(model, brim_tube_config(wall_generator, wall_sequence, change.to_type, change.to_width));
|
|
||||||
print.process();
|
|
||||||
const std::string after = wall_order(tube_loops(print, 3.).front());
|
|
||||||
|
|
||||||
Print fresh_print;
|
|
||||||
Model fresh_model;
|
|
||||||
round_tube(fresh_print, fresh_model, brim_tube_config(wall_generator, wall_sequence, change.to_type, change.to_width), 3.);
|
|
||||||
const std::string fresh = wall_order(tube_loops(fresh_print, 3.).front());
|
|
||||||
|
|
||||||
// The change does turn the first layer around.
|
|
||||||
REQUIRE(before != fresh);
|
|
||||||
CHECK(after == fresh);
|
|
||||||
}
|
|
||||||
@@ -735,65 +735,6 @@ TEST_CASE("Custom G-code placeholders are substituted", "[Print]")
|
|||||||
CHECK(per_layer.find(";Layer:399 ") != std::string::npos);
|
CHECK(per_layer.find(";Layer:399 ") != std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final print statistics are known only after the whole G-code is generated, yet every custom G-code,
|
|
||||||
// the file header included, gets the same values as filename_format.
|
|
||||||
TEST_CASE("Print statistics in custom G-code match the final print statistics", "[Print][Regression]")
|
|
||||||
{
|
|
||||||
const std::string gcode_key = GENERATE(as<std::string>{}, "file_start_gcode", "machine_start_gcode", "machine_end_gcode");
|
|
||||||
// Bambu printers use another tag table and G-code layout.
|
|
||||||
const bool is_bbl = GENERATE(false, true);
|
|
||||||
CAPTURE(gcode_key, is_bbl);
|
|
||||||
|
|
||||||
const std::vector<std::string> number_keys = { "total_weight", "used_filament", "total_cost", "total_wipe_tower_cost",
|
|
||||||
"total_wipe_tower_filament" };
|
|
||||||
const std::vector<std::string> time_keys = { "print_time", "normal_print_time", "silent_print_time" };
|
|
||||||
std::string templ;
|
|
||||||
for (const std::vector<std::string> *keys : { &number_keys, &time_keys })
|
|
||||||
for (const std::string &key : *keys)
|
|
||||||
templ += "; stat " + key + "={" + key + "}\n";
|
|
||||||
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({
|
|
||||||
{ gcode_key, templ },
|
|
||||||
{ "filament_density", "1.25" },
|
|
||||||
{ "filament_cost", "20" },
|
|
||||||
{ "time_cost", "2" },
|
|
||||||
});
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
init_print({ cube(20) }, print, model, config);
|
|
||||||
print.is_BBL_printer() = is_bbl;
|
|
||||||
|
|
||||||
std::string gcode;
|
|
||||||
REQUIRE_NOTHROW(gcode = Slic3r::Test::gcode(print));
|
|
||||||
|
|
||||||
// Match whole lines only: the config block repeats the template on a single line, and Bambu printers
|
|
||||||
// write that block before the start G-code.
|
|
||||||
const std::string lines = "\n" + gcode;
|
|
||||||
const auto value_of = [&lines](const std::string &key) {
|
|
||||||
const std::string prefix = "\n; stat " + key + "=";
|
|
||||||
const size_t start = lines.find(prefix);
|
|
||||||
REQUIRE(start != std::string::npos);
|
|
||||||
const size_t begin = start + prefix.size();
|
|
||||||
return lines.substr(begin, lines.find('\n', begin) - begin);
|
|
||||||
};
|
|
||||||
// The same statistics filename_format is expanded with.
|
|
||||||
DynamicConfig stats = print.print_statistics().config();
|
|
||||||
// A 20mm cube with filament_density 1.25 weighs something, unlike the running extruded_weight_total in the header.
|
|
||||||
CHECK(stats.opt_float("total_weight") > 0.);
|
|
||||||
for (const std::string &key : number_keys) {
|
|
||||||
CAPTURE(key);
|
|
||||||
const std::string value = value_of(key);
|
|
||||||
REQUIRE(value.find('@') == std::string::npos);
|
|
||||||
// Written with two decimals.
|
|
||||||
CHECK_THAT(std::stod(value), Catch::Matchers::WithinAbs(stats.opt_float(key), 0.006));
|
|
||||||
}
|
|
||||||
for (const std::string &key : time_keys) {
|
|
||||||
CAPTURE(key);
|
|
||||||
CHECK(value_of(key) == stats.opt_string(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("export_gcode writes G-code without a result pointer", "[Print][export_gcode]")
|
TEST_CASE("export_gcode writes G-code without a result pointer", "[Print][export_gcode]")
|
||||||
{
|
{
|
||||||
Print print;
|
Print print;
|
||||||
|
|||||||
@@ -985,37 +985,6 @@ TEST_CASE("A user filament synced to the cloud and back keeps its values and uns
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("A project filament storing one value for a per-variant key applies it to every variant", "[Preset][Bundle][Regression]")
|
|
||||||
{
|
|
||||||
PresetCollection filaments(Preset::TYPE_FILAMENT, Preset::filament_options(),
|
|
||||||
static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()));
|
|
||||||
Preset &parent = add_inmemory_preset(filaments, "Parent Filament");
|
|
||||||
parent.config.set_key_value("filament_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard", "Direct Drive High Flow"}));
|
|
||||||
parent.config.set_key_value("adaptive_pressure_advance_model", new ConfigOptionStrings({"parent standard", "parent high flow"}));
|
|
||||||
parent.config.set_deserialize_strict("pressure_advance", "0.02,0.03");
|
|
||||||
parent.is_system = true;
|
|
||||||
|
|
||||||
// A filament saved before these keys became per-variant lists both variants but stores each key once.
|
|
||||||
const std::string model = "0.025,15.6,5000";
|
|
||||||
DynamicPrintConfig config;
|
|
||||||
config.set_key_value(BBL_JSON_KEY_INHERITS, new ConfigOptionString("Parent Filament"));
|
|
||||||
config.set_key_value("filament_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard", "Direct Drive High Flow"}));
|
|
||||||
config.set_key_value("adaptive_pressure_advance_model", new ConfigOptionStrings({model}));
|
|
||||||
config.set_deserialize_strict("pressure_advance", "0.016");
|
|
||||||
auto embedded = std::make_unique<Preset>(Preset::TYPE_FILAMENT, "Project Filament", false);
|
|
||||||
embedded->config = std::move(config);
|
|
||||||
embedded->is_project_embedded = true;
|
|
||||||
|
|
||||||
std::vector<Preset *> project_presets{embedded.get()};
|
|
||||||
PresetsConfigSubstitutions substitutions;
|
|
||||||
filaments.load_project_embedded_presets(project_presets, PRESET_FILAMENT_NAME, substitutions, ForwardCompatibilitySubstitutionRule::Disable);
|
|
||||||
|
|
||||||
const Preset *loaded = filaments.find_preset("Project Filament");
|
|
||||||
REQUIRE(loaded != nullptr);
|
|
||||||
CHECK(loaded->config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values == std::vector<std::string>{model, model});
|
|
||||||
check_double_vector(loaded->config.option<ConfigOptionFloats>("pressure_advance")->values, {0.016, 0.016});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Repeated loads of a user preset directory produce the same presets", "[Preset][Bundle]")
|
TEST_CASE("Repeated loads of a user preset directory produce the same presets", "[Preset][Bundle]")
|
||||||
{
|
{
|
||||||
ScopedTemporaryDir temp_dir;
|
ScopedTemporaryDir temp_dir;
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
|
|||||||
if(NOT DEFINED BBL_INTERNAL_TESTING)
|
if(NOT DEFINED BBL_INTERNAL_TESTING)
|
||||||
set(BBL_INTERNAL_TESTING "0")
|
set(BBL_INTERNAL_TESTING "0")
|
||||||
endif()
|
endif()
|
||||||
set(SoftFever_VERSION "2.5.0-dev")
|
set(SoftFever_VERSION "2.5.0-alpha")
|
||||||
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
|
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
|
||||||
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
|
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
|
||||||
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})
|
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||||
|
|||||||
Reference in new issue
Block a user