Merge Snapmaker v2.3.0 into FullSpectrum

This commit is contained in:
Calogero Guagenti
2026-03-24 17:49:09 +01:00
74 changed files with 114504 additions and 96540 deletions
+1 -1
View File
@@ -254,7 +254,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Deploy Flatpak to nightly release - name: Deploy Flatpak to nightly release
if: ${{github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3'}} if: ${{github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final'}}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
+13 -13
View File
@@ -122,7 +122,7 @@ jobs:
# Thanks to RaySajuuk, it's working now # Thanks to RaySajuuk, it's working now
- name: Sign app and notary - name: Sign app and notary
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || github.ref == 'refs/heads/2.2.3') && inputs.os == 'macos-14' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'macos-14' }}
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
env: env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
@@ -175,7 +175,7 @@ jobs:
fi fi
- name: Create DMG without notary - name: Create DMG without notary
if: ${{ inputs.os == 'macos-14' && (github.repository != 'Snapmaker/OrcaSlicer' || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') && github.ref != 'refs/heads/2.2.3')) }} if: ${{ inputs.os == 'macos-14' && (github.repository != 'Snapmaker/OrcaSlicer' || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') && github.ref != 'refs/heads/2.3.0-final')) }}
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: | run: |
mkdir -p ${{ github.workspace }}/build/universal/Snapmaker_Orca_dmg mkdir -p ${{ github.workspace }}/build/universal/Snapmaker_Orca_dmg
@@ -217,7 +217,7 @@ jobs:
if-no-files-found: warn if-no-files-found: warn
- name: Deploy Mac release - name: Deploy Mac release
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'macos-14' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'macos-14' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
@@ -228,7 +228,7 @@ jobs:
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Check if profile validator DMG exists - name: Check if profile validator DMG exists
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'macos-14' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'macos-14' }}
id: check_dmg id: check_dmg
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: | run: |
@@ -242,7 +242,7 @@ jobs:
shell: bash shell: bash
- name: Deploy Mac Snapmaker_Orca_profile_validator DMG release - name: Deploy Mac Snapmaker_Orca_profile_validator DMG release
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'macos-14' && steps.check_dmg.outputs.exists == 'true' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'macos-14' && steps.check_dmg.outputs.exists == 'true' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
@@ -318,7 +318,7 @@ jobs:
path: ${{ github.workspace }}/build/src/Release/Snapmaker_Orca_profile_validator.exe path: ${{ github.workspace }}/build/src/Release/Snapmaker_Orca_profile_validator.exe
- name: Deploy Windows release portable - name: Deploy Windows release portable
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'windows-latest' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'windows-latest' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
@@ -329,7 +329,7 @@ jobs:
max_releases: 1 max_releases: 1
- name: Deploy Windows release installer - name: Deploy Windows release installer
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'windows-latest' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'windows-latest' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
@@ -340,7 +340,7 @@ jobs:
max_releases: 1 max_releases: 1
- name: Deploy Windows Snapmaker_Orca_profile_validator release - name: Deploy Windows Snapmaker_Orca_profile_validator release
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'windows-latest' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'windows-latest' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
@@ -387,7 +387,7 @@ jobs:
- name: Build orca_custom_preset_tests - name: Build orca_custom_preset_tests
id: custom_preset_tests id: custom_preset_tests
if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.1') && inputs.os == 'ubuntu-24.04' }} if: ${{ github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'ubuntu-24.04' }}
working-directory: ${{ github.workspace }}/build/src/Release working-directory: ${{ github.workspace }}/build/src/Release
shell: bash shell: bash
run: | run: |
@@ -432,7 +432,7 @@ jobs:
path: './build/src/Release/Snapmaker_Orca_profile_validator' path: './build/src/Release/Snapmaker_Orca_profile_validator'
- name: Deploy Ubuntu release - name: Deploy Ubuntu release
if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && (inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04') }} if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && (inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04') }}
env: env:
ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }} ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
@@ -444,7 +444,7 @@ jobs:
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
- name: Deploy Ubuntu release - name: Deploy Ubuntu release
if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && inputs.os == 'ubuntu-24.04' }} if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'ubuntu-24.04' }}
uses: rickstaa/action-create-tag@v1 uses: rickstaa/action-create-tag@v1
with: with:
tag: "nightly-builds" tag: "nightly-builds"
@@ -453,7 +453,7 @@ jobs:
message: "nightly-builds" message: "nightly-builds"
- name: Deploy Ubuntu Snapmaker_Orca_profile_validator release - name: Deploy Ubuntu Snapmaker_Orca_profile_validator release
if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.3') && (inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04') }} if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && (inputs.os == 'ubuntu-20.04' || inputs.os == 'ubuntu-24.04') }}
env: env:
ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }} ubuntu-ver-str: ${{ (inputs.os == 'ubuntu-24.04' && '_Ubuntu2404') || '' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
@@ -466,7 +466,7 @@ jobs:
max_releases: 1 max_releases: 1
- name: Deploy orca_custom_preset_tests - name: Deploy orca_custom_preset_tests
if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.2.1') && inputs.os == 'ubuntu-24.04' && steps.custom_preset_tests.outputs.created == 'true' }} if: ${{ ! env.ACT && github.repository == 'Snapmaker/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/2.3.0-final') && inputs.os == 'ubuntu-24.04' && steps.custom_preset_tests.outputs.created == 'true' }}
uses: WebFreak001/deploy-nightly@v3.2.0 uses: WebFreak001/deploy-nightly@v3.2.0
with: with:
upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label} upload_url: https://uploads.github.com/repos/Snapmaker/OrcaSlicer/releases/169912305/assets{?name,label}
+6
View File
@@ -93,6 +93,10 @@ echo " - CMAKE_GENERATOR: $SLICER_CMAKE_GENERATOR for Slicer, $DEPS_CMAKE_GENERA
echo " - OSX_DEPLOYMENT_TARGET: $OSX_DEPLOYMENT_TARGET" echo " - OSX_DEPLOYMENT_TARGET: $OSX_DEPLOYMENT_TARGET"
echo echo
# CMake 4.x compatibility workaround for deps/bootstrap projects that still
# declare an old minimum version.
export CMAKE_POLICY_VERSION_MINIMUM=3.5
# if which -s brew; then # if which -s brew; then
# brew --prefix libiconv # brew --prefix libiconv
# brew --prefix zstd # brew --prefix zstd
@@ -129,11 +133,13 @@ function build_deps() {
( (
set -x set -x
mkdir -p "$DEPS" mkdir -p "$DEPS"
mkdir -p "$DEPS_DIR/DL_CACHE"
cd "$DEPS_BUILD_DIR" cd "$DEPS_BUILD_DIR"
if [ "1." != "$BUILD_ONLY". ]; then if [ "1." != "$BUILD_ONLY". ]; then
cmake "${DEPS_DIR}" \ cmake "${DEPS_DIR}" \
-G "${DEPS_CMAKE_GENERATOR}" \ -G "${DEPS_CMAKE_GENERATOR}" \
-DDESTDIR="$DEPS" \ -DDESTDIR="$DEPS" \
-DDEP_DOWNLOAD_DIR="$DEPS_DIR/DL_CACHE" \
-DOPENSSL_ARCH="darwin64-${_ARCH}-cc" \ -DOPENSSL_ARCH="darwin64-${_ARCH}-cc" \
-DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \ -DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \ -DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
+204
View File
@@ -0,0 +1,204 @@
; [1] PACK_SOURCE_DIR = compile-time only (e.g. .\build\Snapmaker_Orca). [2] INSTALL_DIR_RUNTIME = runtime install dir (default .\ = $EXEDIR).
!include "MUI2.nsh"
!include "FileFunc.nsh"
!include "LogicLib.nsh"
!define PRODUCT_NAME "Snapmaker Orca"
!define PRODUCT_PUBLISHER "Snapmaker"
!define PRODUCT_WEB_SITE "https://github.com/Snapmaker/OrcaSlicer"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
!define PRODUCT_INSTALL_KEY "Software\${PRODUCT_PUBLISHER}\${PRODUCT_NAME}"
!ifndef VERSION
!define VERSION "2.2.3"
!endif
!ifndef SOURCE_DIR
!define SOURCE_DIR ".\build\Snapmaker_Orca"
!endif
!define PACK_SOURCE_DIR "${SOURCE_DIR}"
; 64-bit app: use PROGRAMFILES64 so default path is C:\Program Files\Snapmaker_Orca, not (x86)
!define INSTALL_DIR_RUNTIME "$PROGRAMFILES64\Snapmaker_Orca"
InstallDir "${INSTALL_DIR_RUNTIME}"
!ifndef OUTPUT_FILE
!define OUTPUT_FILE "Snapmaker_Orca_Windows_Installer_V${VERSION}.exe"
!endif
; License page: show LICENSE.txt from repo root (same dir as this .nsi)
!ifndef LICENSE_FILE
!define LICENSE_FILE ".\LICENSE.txt"
!endif
RequestExecutionLevel admin
; No /SOLID to avoid "Internal compiler error #12345: error mmapping datablock"
SetCompressor lzma
VIProductVersion "${VERSION}.0"
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
VIAddVersionKey "Comments" "Snapmaker Orca is an open source slicer for FDM printers"
VIAddVersionKey "CompanyName" "${PRODUCT_PUBLISHER}"
VIAddVersionKey "LegalCopyright" "Copyright (C) ${PRODUCT_PUBLISHER}"
VIAddVersionKey "FileDescription" "${PRODUCT_NAME} ${VERSION} Installer"
VIAddVersionKey "FileVersion" "${VERSION}"
VIAddVersionKey "ProductVersion" "${VERSION}"
VIAddVersionKey "InternalName" "${PRODUCT_NAME}"
VIAddVersionKey "LegalTrademarks" ""
VIAddVersionKey "OriginalFilename" "${OUTPUT_FILE}"
; Installer and uninstaller icon: set by build_and_pack.bat via /DICON_FILE=path (e.g. Snapmaker_Orca.ico or snapmaker.ico)
!ifdef ICON_FILE
!define MUI_ICON "${ICON_FILE}"
!define MUI_UNICON "${ICON_FILE}"
!else
!define MUI_ICON ".\resources\images\Snapmaker_Orca.ico"
!define MUI_UNICON ".\resources\images\Snapmaker_Orca.ico"
!endif
!define MUI_WELCOMEPAGE_TITLE "Welcome to ${PRODUCT_NAME} Setup"
!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of ${PRODUCT_NAME} ${VERSION}.$\r$\n$\r$\nClick Next to continue."
!insertmacro MUI_PAGE_WELCOME
!ifdef LICENSE_FILE
!define MUI_LICENSEPAGE_CHECKBOX
!insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}"
!endif
!insertmacro MUI_PAGE_COMPONENTS
!define MUI_DIRECTORYPAGE_TEXT_TOP "Choose the folder in which to install ${PRODUCT_NAME}."
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}"
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchApp"
!define MUI_FINISHPAGE_LINK "Visit ${PRODUCT_NAME} website"
!define MUI_FINISHPAGE_LINK_LOCATION "${PRODUCT_WEB_SITE}"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_LANGUAGE "English"
Name "${PRODUCT_NAME} ${VERSION}"
OutFile "${OUTPUT_FILE}"
Section "Main program" SecMain
SectionIn RO
SetOutPath "$INSTDIR"
DetailPrint "Installing ${PRODUCT_NAME}..."
DetailPrint "Target dir: $INSTDIR"
DetailPrint "Copying files..."
; PACK_SOURCE_DIR = compile time only. At runtime this File extracts from embedded payload to $INSTDIR. Exclude include and lib dirs.
File /r /x "*.pdb" /x "*.ilk" /x "*.exp" /x "*.lib" /x "*.obj" /x "*.idb" /x "*.tlog" /x "*.h" /x "*.hpp" /x "*.c" /x "*.cpp" /x "*.cxx" /x "*.cc" /x "*.vcxproj" /x "*.vcxproj.filters" /x "*.sln" /x "*.cmake" /x "*.py" /x "*.md" /x "*.vcxproj.user" /x "CMakeFiles" /x "RelWithDebInfo" /x "Debug" /x "MinSizeRel" /x ".vs" /x "vcpkg_installed" /x "*.dir" /x "include\*" /x "lib\*" "${PACK_SOURCE_DIR}\*.*"
IfFileExists "$INSTDIR\snapmaker-orca.exe" 0 extract_error
DetailPrint "Creating uninstaller..."
WriteUninstaller "$INSTDIR\Uninstall.exe"
DetailPrint "Writing registry..."
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\snapmaker-orca.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "HelpLink" "${PRODUCT_WEB_SITE}"
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "NoModify" 1
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "NoRepair" 1
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_INSTALL_KEY}" "Version" "${VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_INSTALL_KEY}" "InstallPath" "$INSTDIR"
DetailPrint "Installation complete!"
Goto end_section
extract_error:
MessageBox MB_OK|MB_ICONSTOP "Installation failed: snapmaker-orca.exe was not found in the package. The installer may be corrupted."
Abort
end_section:
SectionEnd
Section "Desktop shortcut" SecDesktop
DetailPrint "Creating desktop shortcut..."
CreateShortcut "$DESKTOP\Snapmaker Orca.lnk" "$INSTDIR\snapmaker-orca.exe" "" "$INSTDIR\snapmaker-orca.exe" 0
SectionEnd
Section "Start menu shortcut" SecStartMenu
DetailPrint "Creating start menu shortcut..."
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}\Snapmaker Orca.lnk" "$INSTDIR\snapmaker-orca.exe" "" "$INSTDIR\snapmaker-orca.exe" 0
CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
SectionEnd
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecMain} "Install ${PRODUCT_NAME} and all required files."
!insertmacro MUI_DESCRIPTION_TEXT ${SecDesktop} "Create a desktop shortcut for ${PRODUCT_NAME}."
!insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} "Create a start menu shortcut for ${PRODUCT_NAME}."
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Section "Uninstall"
DetailPrint "Uninstalling ${PRODUCT_NAME}..."
DetailPrint "Checking for running processes..."
nsExec::ExecToLog 'taskkill /F /IM snapmaker-orca.exe /T'
Sleep 500
DetailPrint "Removing desktop shortcut..."
Delete "$DESKTOP\Snapmaker Orca.lnk"
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
DetailPrint "Removing start menu shortcut..."
RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}"
DetailPrint "Removing install directory..."
RMDir /r /REBOOTOK "$INSTDIR"
RMDir "$INSTDIR"
DetailPrint "Removing registry entries..."
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_INSTALL_KEY}"
DeleteRegKey HKCU "${PRODUCT_INSTALL_KEY}"
DetailPrint "Uninstall complete!"
SectionEnd
Function LaunchApp
ExecShell "open" "$INSTDIR\snapmaker-orca.exe"
FunctionEnd
Function .onInit
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString"
StrCmp $R0 "" done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${PRODUCT_NAME} is already installed.$\n$\nClick OK to uninstall the old version, or Cancel to abort." \
IDOK uninst
Abort
uninst:
ClearErrors
ExecWait '$R0 _?=$INSTDIR'
IfErrors no_remove_uninstaller done
no_remove_uninstaller:
done:
FunctionEnd
+26 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "Snapmaker", "name": "Snapmaker",
"version": "02.02.43.02", "version": "02.02.44.02",
"force_update": "0", "force_update": "0",
"description": "Snapmaker configurations", "description": "Snapmaker configurations",
"machine_model_list": [ "machine_model_list": [
@@ -1322,8 +1322,32 @@
{ {
"name": "Snapmaker TPU 95A @U1 base", "name": "Snapmaker TPU 95A @U1 base",
"sub_path":"filament/Snapmaker TPU 95A @U1 base.json" "sub_path":"filament/Snapmaker TPU 95A @U1 base.json"
} },
{
"name": "fdm_filament_common_poly",
"sub_path": "filament/fdm_filament_common_poly.json"
},
{
"name": "fdm_filament_pla_poly",
"sub_path": "filament/fdm_filament_pla_poly.json"
},
{
"name": "Polymaker PLA @U1 base",
"sub_path": "filament/Polymaker PLA @U1 base.json"
},
{
"name": "Polymaker General PLA Family @U1",
"sub_path": "filament/Polymaker General PLA Family @U1.json"
},
{
"name": "Polymaker Silk PLA Family @U1",
"sub_path": "filament/Polymaker Silk PLA Family @U1.json"
},
{
"name": "Polymaker Tough PLA Family @U1",
"sub_path": "filament/Polymaker Tough PLA Family @U1.json"
}
], ],
"machine_list": [ "machine_list": [
{ {
@@ -0,0 +1,35 @@
{
"type": "filament",
"instantiation": "true",
"setting_id": "POLY_GENERAL_PLA_U1_001",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"enable_pressure_advance": [
"1"
],
"filament_max_volumetric_speed": [
"15"
],
"filament_settings_id": [
"Polymaker General PLA Family @U1"
],
"filament_vendor": [
"Polymaker"
],
"from": "system",
"inherits": "Polymaker PLA @U1 base",
"name": "Polymaker General PLA Family @U1",
"nozzle_temperature_range_high": [
"230"
],
"temperature_vitrification": [
"62"
],
"textured_plate_temp": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
]
}
@@ -0,0 +1,11 @@
{
"type": "filament",
"name": "Polymaker PLA @U1 base",
"inherits": "fdm_filament_pla_poly",
"from": "system",
"setting_id": "OGFSA04",
"instantiation": "false",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
]
}
@@ -0,0 +1,35 @@
{
"type": "filament",
"instantiation": "true",
"setting_id": "POLY_SILK_PLA_U1_001",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_density": [
"1.34"
],
"filament_settings_id": [
"Polymaker Silk PLA Family @U1"
],
"filament_vendor": [
"Polymaker"
],
"from": "system",
"inherits": "Polymaker PLA @U1 base",
"name": "Polymaker Silk PLA Family @U1",
"nozzle_temperature": [
"230"
],
"nozzle_temperature_initial_layer": [
"230"
],
"nozzle_temperature_range_high": [
"230"
],
"textured_plate_temp": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
]
}
@@ -0,0 +1,35 @@
{
"type": "filament",
"instantiation": "true",
"setting_id": "POLY_TOUGH_PLA_U1_001",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_density": [
"1.23"
],
"filament_flow_ratio": [
"0.96"
],
"filament_settings_id": [
"Polymaker Tough PLA Family @U1"
],
"filament_vendor": [
"Polymaker"
],
"from": "system",
"inherits": "Polymaker PLA @U1 base",
"name": "Polymaker Tough PLA Family @U1",
"slow_down_layer_time": [
"6"
],
"temperature_vitrification": [
"55"
],
"textured_plate_temp": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
]
}
@@ -0,0 +1,187 @@
{
"type": "filament",
"name": "fdm_filament_common_poly",
"from": "system",
"instantiation": "false",
"activate_air_filtration": [
"0"
],
"chamber_temperatures": [
"0"
],
"close_fan_the_first_x_layers": [
"3"
],
"complete_print_exhaust_fan_speed": [
"70"
],
"cool_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"60"
],
"during_print_exhaust_fan_speed": [
"70"
],
"eng_plate_temp": [
"60"
],
"eng_plate_temp_initial_layer": [
"60"
],
"fan_cooling_layer_time": [
"60"
],
"fan_max_speed": [
"100"
],
"fan_min_speed": [
"35"
],
"filament_cost": [
"0"
],
"filament_density": [
"0"
],
"filament_deretraction_speed": [
"nil"
],
"filament_diameter": [
"1.75"
],
"filament_flow_ratio": [
"1"
],
"filament_is_support": [
"0"
],
"filament_long_retractions_when_cut": [
"nil"
],
"filament_max_volumetric_speed": [
"0"
],
"filament_minimal_purge_on_wipe_tower": [
"15"
],
"filament_retract_before_wipe": [
"nil"
],
"filament_retract_restart_extra": [
"nil"
],
"filament_retract_when_changing_layer": [
"nil"
],
"filament_retraction_distances_when_cut": [
"nil"
],
"filament_retraction_length": [
"nil"
],
"filament_retraction_minimum_travel": [
"nil"
],
"filament_retraction_speed": [
"nil"
],
"filament_settings_id": [
""
],
"filament_soluble": [
"0"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"Generic"
],
"filament_wipe": [
"nil"
],
"filament_wipe_distance": [
"nil"
],
"filament_z_hop": [
"nil"
],
"filament_z_hop_types": [
"nil"
],
"full_fan_speed_layer": [
"0"
],
"filament_scarf_seam_type": [
"none"
],
"filament_scarf_height": [
"10%"
],
"filament_scarf_gap": [
"0%"
],
"filament_scarf_length": [
"10"
],
"filament_shrink": [
"100%"
],
"hot_plate_temp": [
"60"
],
"hot_plate_temp_initial_layer": [
"60"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"200"
],
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"95%"
],
"reduce_fan_stop_start_freq": [
"0"
],
"required_nozzle_HRC": [
"3"
],
"supertack_plate_temp": [
"45"
],
"supertack_plate_temp_initial_layer": [
"45"
],
"slow_down_for_layer_cooling": [
"1"
],
"slow_down_layer_time": [
"8"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"100"
],
"textured_plate_temp": [
"60"
],
"textured_plate_temp_initial_layer": [
"60"
],
"filament_start_gcode": [
"; Filament gcode\n"
],
"filament_end_gcode": [
"; filament end gcode\n"
],
"compatible_printers": []
}
@@ -0,0 +1,89 @@
{
"type": "filament",
"name": "fdm_filament_pla_poly",
"inherits": "fdm_filament_common_poly",
"from": "system",
"filament_id": "OGFL99",
"instantiation": "false",
"additional_cooling_fan_speed": [
"70"
],
"close_fan_the_first_x_layers": [
"1"
],
"cool_plate_temp": [
"35"
],
"cool_plate_temp_initial_layer": [
"35"
],
"eng_plate_temp": [
"0"
],
"eng_plate_temp_initial_layer": [
"0"
],
"fan_cooling_layer_time": [
"100"
],
"fan_min_speed": [
"100"
],
"filament_cost": [
"20"
],
"filament_density": [
"1.24"
],
"filament_max_volumetric_speed": [
"12"
],
"filament_scarf_seam_type": [
"none"
],
"filament_scarf_gap": [
"15%"
],
"hot_plate_temp": [
"55"
],
"hot_plate_temp_initial_layer": [
"55"
],
"nozzle_temperature": [
"220"
],
"nozzle_temperature_initial_layer": [
"220"
],
"nozzle_temperature_range_low": [
"190"
],
"nozzle_temperature_range_high": [
"240"
],
"overhang_fan_threshold": [
"50%"
],
"reduce_fan_stop_start_freq": [
"1"
],
"slow_down_layer_time": [
"4"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"45"
],
"textured_plate_temp": [
"55"
],
"textured_plate_temp_initial_layer": [
"55"
],
"filament_flow_ratio": [
"0.98"
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+264
View File
@@ -853,6 +853,7 @@ engine
spring_animation spring_animation
tonic tonic
txt txt
url_launcher_web
web_test_fonts web_test_fonts
web_unicode web_unicode
@@ -1975,6 +1976,7 @@ ansicolor
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
args args
csslib csslib
logging
Copyright 2013, the Dart project authors. Copyright 2013, the Dart project authors.
@@ -5368,6 +5370,7 @@ SUCH DAMAGE.
cross_file cross_file
flutter_lints flutter_lints
flutter_plugin_android_lifecycle flutter_plugin_android_lifecycle
go_router
multicast_dns multicast_dns
path_provider path_provider
path_provider_android path_provider_android
@@ -5383,6 +5386,13 @@ shared_preferences_linux
shared_preferences_platform_interface shared_preferences_platform_interface
shared_preferences_web shared_preferences_web
shared_preferences_windows shared_preferences_windows
url_launcher
url_launcher_android
url_launcher_ios
url_launcher_linux
url_launcher_macos
url_launcher_platform_interface
url_launcher_windows
vector_graphics vector_graphics
vector_graphics_codec vector_graphics_codec
vector_graphics_compiler vector_graphics_compiler
@@ -8504,6 +8514,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
--------------------------------------------------------------------------------
flutter_html
MIT License
Copyright (c) 2019-2022 The flutter_html developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
flutter_image_compress flutter_image_compress
flutter_image_compress_common flutter_image_compress_common
@@ -32846,6 +32881,30 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
list_counter
MIT License
Copyright (c) 2022 The list_counter developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
loading_animation_widget loading_animation_widget
@@ -34223,6 +34282,211 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
platform_detect
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Workiva Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
pointycastle pointycastle
@@ -79,6 +79,101 @@
"check_network": "Check Network" "check_network": "Check Network"
} }
}, },
"filament_official_color": {
"34061": "Pearl White",
"34062": "Black",
"34063": "Grey",
"34064": "Blue",
"34065": "Red",
"34066": "Purple",
"34067": "Orange",
"34068": "Green",
"34069": "Yellow",
"34070": "Brown",
"34071": "Pink",
"34072": "Cream",
"34073": "Cool White",
"34074": "Magenta",
"34075": "Sky Blue",
"34076": "Ivory White",
"34077": "Lava Orange",
"34078": "Sunburst Yellow",
"34079": "Carbon Black",
"34080": "Moss Mist",
"34081": "Pine Green",
"34082": "Engine Red",
"34083": "Ash Grey",
"34084": "Celestial Blue",
"34085": "Sakura Pink",
"34086": "Watermelon Red",
"34087": "Marine Blue",
"34088": "Latte Brown",
"34089": "Butter Cream",
"34090": "Violet Purple",
"34112": "Bright Yellow",
"34113": "Olive Green",
"34114": "Blue Gray",
"34115": "Warm Beige",
"34116": "Cocoa Brown",
"34137": "Silver",
"34138": "Garnet Red",
"34139": "Pearl Pink",
"34140": "Sapphire Blue",
"34141": "Gold",
"34142": "Copper",
"34157": "Black",
"34158": "White",
"34159": "Light Gray",
"34160": "Red",
"34161": "Orange",
"34162": "Cobalt Blue",
"34163": "Silver Gray",
"34164": "Violet",
"34165": "Deep Purple",
"34166": "Cocoa Brown",
"34176": "Black",
"34177": "White",
"34178": "Silver Gray",
"34179": "Brown",
"34180": "Bright Blue",
"34181": "Red",
"34182": "Dark Gray",
"34183": "Yellow",
"34184": "Green"
},
"filament_color": {
"#FFFFFFFF": "White",
"#E0E0E0FF": "Light Gray",
"#8C9099FF": "Grey",
"#000000FF": "Black",
"#6F4C2FFF": "Brown",
"#FEE5A5FF": "Butter Cream",
"#F4C032FF": "Yellow",
"#F8F81CFF": "Fluorescent Yellow",
"#BEC9A5FF": "Gray Green",
"#2D9E59FF": "Green",
"#39FF14FF": "Fluorescent Green",
"#1B5E20FF": "Dark Green",
"#8BD5EEFF": "Light Blue",
"#4DB6ACFF": "Teal",
"#1E88E5FF": "Blue",
"#003776FF": "Dark Blue",
"#6C5BB1FF": "Blue Violet",
"#8E24AAFF": "Purple",
"#D81B60FF": "Magenta",
"#F55A7CFF": "Pink",
"#E72F1DFF": "Red",
"#F3D6E5FF": "Light Pink",
"#F78E0EFF": "Orange",
"#E65100FF": "Deep Orange"
},
"model_detail": {
"introduction": "Model Description",
"copyright_title": "Copyright Notice",
"published_at": "Published: {}",
"expand_all": "Expand All",
"print_preparation": "Print Configuration"
},
"title": "Discovery Device", "title": "Discovery Device",
"add device": "Add Device", "add device": "Add Device",
"input_ip_address": "Enter IP address", "input_ip_address": "Enter IP address",
@@ -238,18 +333,6 @@
"Unbind Device": "Unbind Devices", "Unbind Device": "Unbind Devices",
"Colors": "Colors", "Colors": "Colors",
"Filaments": "Filaments", "Filaments": "Filaments",
"color_name_black": "Black",
"color_name_white": "White",
"color_name_red": "Red",
"color_name_green": "Green",
"color_name_blue": "Blue",
"color_name_yellow": "Yellow",
"color_name_cyan": "Cyan",
"color_name_purple": "Purple",
"color_name_gray": "Gray",
"color_name_orange": "Orange",
"color_name_brown": "Brown",
"color_name_pink": "Pink",
"Select Color": "Select Color", "Select Color": "Select Color",
"Select Filament": "Select Filament", "Select Filament": "Select Filament",
"Main Cooling Fan Speed": "Main Cooling Fan Speed", "Main Cooling Fan Speed": "Main Cooling Fan Speed",
@@ -302,7 +385,32 @@
"dialog_filament_type_not_match_tips": "This nozzle filament type does not match the preset filament type of the model. Please replace this nozzle filament type or change the preset filament type", "dialog_filament_type_not_match_tips": "This nozzle filament type does not match the preset filament type of the model. Please replace this nozzle filament type or change the preset filament type",
"dialog_filament_type_none_tips": "Filaments were not identified. Please mount the filaments on the device or edit the filament information", "dialog_filament_type_none_tips": "Filaments were not identified. Please mount the filaments on the device or edit the filament information",
"Features are under development, please stay tuned": "Features are under development, please stay tuned", "Features are under development, please stay tuned": "Features are under development, please stay tuned",
"Model": "Model",
"Coming soon...": "Coming soon...",
"Search models...": "Search models...",
"Library": "Library",
"Models": "Models",
"Model Detail": "Model Detail",
"Published": "Published",
"Collect": "Collect",
"Download and Open": "Download & Open",
"Print Now": "Print Now",
"direct_print_tips": "“Print Now” is only available in Cloud Mode",
"Case tags": "Case tags",
"Description": "Description",
"Copyright": "Copyright",
"model_detail_license_title": "Standard Digital File License",
"model_detail_license_description": "This license restricts the use, distribution and commercial use of the digital file or its 3D object. Please comply with the relevant terms.",
"Weight": "Weight",
"Time": "Time",
"Layers": "Layers",
"Height": "Height",
"Profiles": "Profiles",
"Image display": "Image display",
"No models found": "No models found",
"Try a different search term": "Try a different search term",
"Retry": "Retry",
"Print Preprocessing": "Print Preprocessing",
"delete_selected_files_confirm_title": "Confirm Delete Selected Files", "delete_selected_files_confirm_title": "Confirm Delete Selected Files",
"delete_selected_files_confirm_message": "Are you sure you want to delete the selected files? This action cannot be undone.", "delete_selected_files_confirm_message": "Are you sure you want to delete the selected files? This action cannot be undone.",
"clear_all_files_confirm_title": "Confirm Clear All Files", "clear_all_files_confirm_title": "Confirm Clear All Files",
@@ -331,7 +439,9 @@
"camera_auto_shutdown_message": "The camera has been unused for a long time. To reduce power consumption, it has automatically shut down.", "camera_auto_shutdown_message": "The camera has been unused for a long time. To reduce power consumption, it has automatically shut down.",
"got_it": "Got it", "got_it": "Got it",
"monitoring_module_hibernated": "Note : Monitoring Module hibernated. Click Play to wake it up.", "monitoring_module_hibernated": "Note : Monitoring Module hibernated. Click Play to wake it up.",
"Plates": "Plates",
"Print Plates": "Print Plates",
"Plate": "Plate",
"Check": "Check", "Check": "Check",
"default_exception_title": "Exception", "default_exception_title": "Exception",
"Error Code": "Error Code", "Error Code": "Error Code",
@@ -1146,5 +1256,22 @@
"error_0002053200000001_title": "Possible Foreign Object Detected on Print Bed", "error_0002053200000001_title": "Possible Foreign Object Detected on Print Bed",
"error_0002053200000001_desc": "Please check and remove any foreign objects. If none are found, click Continue to resume printing.", "error_0002053200000001_desc": "Please check and remove any foreign objects. If none are found, click Continue to resume printing.",
"error_0002053200000002_title": "Possible Spaghetti Defect Detected", "error_0002053200000002_title": "Possible Spaghetti Defect Detected",
"error_0002053200000002_desc": "Please inspect the model. If any defects are acceptable—or none are found—click Continue to resume printing." "error_0002053200000002_desc": "Please inspect the model. If any defects are acceptable—or none are found—click Continue to resume printing.",
"Load More": "Load More",
"No More Data": "No More Data",
"Error Occurred": "Error Occurred",
"No Models Found": "No Models Found",
"Not Implemented Yet": "Not implemented yet, please try again later",
"Nozzle": "Nozzle",
"Model Library": "Model Library",
"Recommended for You": "Recommended for You",
"units": "units",
"Total": "Total",
"Filament List": "Filament List",
"Collapse": "Collapse",
"Expand": "Expand",
"Untitled Config": "Untitled Config",
"Display Area": "Display Area",
"Print Now is only available in Cloud Mode": "Print Now is only available in Cloud Mode",
"Next": "Next"
} }
@@ -79,6 +79,102 @@
"check_network": "检查网络" "check_network": "检查网络"
} }
}, },
"filament_official_color": {
"34061": "珍珠白",
"34062": "黑色",
"34063": "灰色",
"34064": "蓝色",
"34065": "红色",
"34066": "紫色",
"34067": "橙色",
"34068": "绿色",
"34069": "黄色",
"34070": "棕色",
"34071": "粉色",
"34072": "奶油白",
"34073": "冷白色",
"34074": "品红色",
"34075": "天空蓝",
"34076": "骨白色",
"34077": "熔岩橙",
"34078": "日曜黄",
"34079": "碳素黑",
"34080": "苔雾绿",
"34081": "松木绿",
"34082": "引擎红",
"34083": "烬灰色",
"34084": "天幕蓝",
"34085": "樱花粉",
"34086": "西瓜红",
"34087": "海蓝色",
"34088": "拿铁棕",
"34089": "奶油黄",
"34090": "紫罗兰",
"34112": "亮黄色",
"34113": "橄榄绿",
"34114": "蓝灰色",
"34115": "暖米色",
"34116": "可可棕",
"34137": "银色",
"34138": "石榴红",
"34139": "珠光粉",
"34140": "宝石蓝",
"34141": "金色",
"34142": "铜色",
"34157": "黑色",
"34158": "白色",
"34159": "浅灰色",
"34160": "红色",
"34161": "橙色",
"34162": "钴蓝色",
"34163": "银灰色",
"34164": "紫罗兰",
"34165": "深紫色",
"34166": "可可棕",
"34176": "黑色",
"34177": "白色",
"34178": "银灰色",
"34179": "棕色",
"34180": "亮蓝色",
"34181": "红色",
"34182": "深灰色",
"34183": "黄色",
"34184": "绿色"
},
"filament_color": {
"#FFFFFFFF": "白色",
"#E0E0E0FF": "浅灰色",
"#8C9099FF": "灰色",
"#000000FF": "黑色",
"#6F4C2FFF": "棕色",
"#FEE5A5FF": "奶油黄",
"#F4C032FF": "黄色",
"#F8F81CFF": "荧光黄色",
"#BEC9A5FF": "灰绿色",
"#2D9E59FF": "绿色",
"#39FF14FF": "荧光绿色",
"#1B5E20FF": "深绿色",
"#8BD5EEFF": "浅蓝色",
"#4DB6ACFF": "青绿色",
"#1E88E5FF": "蓝色",
"#003776FF": "深蓝色",
"#6C5BB1FF": "蓝紫色",
"#8E24AAFF": "紫色",
"#D81B60FF": "品红色",
"#F55A7CFF": "粉红色",
"#E72F1DFF": "红色",
"#F3D6E5FF": "浅粉色",
"#F78E0EFF": "橙色",
"#E65100FF": "深橙色"
},
"model_detail": {
"introduction": "模型描述",
"copyright_title": "版权声明",
"published_at": "发布于:{}",
"expand_all": "展开全部",
"print_preparation": "打印配置"
},
"The print task was sent successfully": "打印任务发送成功",
"title": "发现设备", "title": "发现设备",
"description": "发现设备", "description": "发现设备",
"add device": "添加设备", "add device": "添加设备",
@@ -230,6 +326,7 @@
"Pull down refresh": "下拉刷新", "Pull down refresh": "下拉刷新",
"Click to load more": "点击加载更多", "Click to load more": "点击加载更多",
"User not logged in": "用户未登录", "User not logged in": "用户未登录",
"Not logged in account": "未登录账户", "Not logged in account": "未登录账户",
@@ -292,22 +389,19 @@
"Camera": "摄像机", "Camera": "摄像机",
"File information": "文件信息", "File information": "文件信息",
"Model information": "模型信息", "Model Information": "模型信息",
"Estimated material": "预估用料",
"Estimated time": "预估用时",
"File name": "文件名称",
"Select printer": "选择打印机", "Select Printer": "选择打印机",
"Edit filament": "编辑耗材", "Edit Filament": "编辑耗材",
"Select Tool Head": "选择打印头", "Select Tool Head": "选择打印头",
"Please enter the correct number": "请输入正确的数字", "Please enter the correct number": "请输入正确的数字",
"Preference setting": "偏好设置", "Print Preferences": "偏好设置",
"Auto leveling": "自动调平", "Auto Leveling": "自动调平",
"Time-lapse camera": "延时摄影", "Time-lapse Camera": "延时摄影",
"Bed leveling": "热床调平", "Bed leveling": "热床调平",
"Flow calibrate": "流量校准", "Flow calibrate": "流量校准",
"Extrusion flow calibrate": "挤出流量校准", "Extrusion Flow Calibration": "挤出流量校准",
"When enabled, the printer will automatically calibrate flow compensation before printing. Recommended after each filament change.": "启用后,打印机将在打印前自动校准流量补偿。每次更换丝材后建议执行。", "When enabled, the printer will automatically calibrate flow compensation before printing. Recommended after each filament change.": "启用后,打印机将在打印前自动校准流量补偿。每次更换丝材后建议执行。",
"Please select filament type": "请选择耗材类型", "Please select filament type": "请选择耗材类型",
@@ -426,19 +520,6 @@
"Unbind Device": "解绑设备", "Unbind Device": "解绑设备",
"Colors": "颜色", "Colors": "颜色",
"Filaments": "耗材", "Filaments": "耗材",
"color_name_black": "黑色",
"color_name_white": "白色",
"color_name_red": "红色",
"color_name_green": "绿色",
"color_name_blue": "蓝色",
"color_name_yellow": "黄色",
"color_name_cyan": "青色",
"color_name_purple": "紫色",
"color_name_gray": "灰色",
"color_name_orange": "橙色",
"color_name_brown": "棕色",
"color_name_pink": "粉色",
"Select Color": "选择颜色",
"Select Filament": "选择耗材", "Select Filament": "选择耗材",
"Main Cooling Fan Speed": "主散热风扇风速", "Main Cooling Fan Speed": "主散热风扇风速",
"Assist Cooling Fan Speed": "辅助散热风扇风速", "Assist Cooling Fan Speed": "辅助散热风扇风速",
@@ -482,29 +563,6 @@
"Current filament not loaded, please load filament first": "当前未装载耗材,请先装料", "Current filament not loaded, please load filament first": "当前未装载耗材,请先装料",
"Loading filament config...": "加载耗材配置中...", "Loading filament config...": "加载耗材配置中...",
"Ivory White": "象牙白",
"Carbon Black": "碳黑",
"Lava Orange": "熔岩橙",
"Sunburst Yellow": "日落黄",
"Moss Mist": "苔藓灰",
"Pine Green": "松绿",
"Engine Red": "引擎红",
"Ash Grey": "灰烬灰",
"Celestial Blue": "天蓝",
"Sakura Pink": "樱花粉",
"Pearl White": "珍珠白",
"Black": "黑色",
"Grey": "灰色",
"Blue": "蓝色",
"Red": "红色",
"Purple": "紫色",
"Orange": "橙色",
"Brown": "棕色",
"Pink": "粉色",
"Green": "绿色",
"Yellow": "黄色",
"White": "白色",
"Sky Blue": "天蓝",
"Time remaining": "剩余时间", "Time remaining": "剩余时间",
"N/A": "N/A", "N/A": "N/A",
@@ -555,6 +613,30 @@
"dialog_filament_type_none_tips": "未识别到耗材,请在设备上挂载耗材或编辑耗材信息", "dialog_filament_type_none_tips": "未识别到耗材,请在设备上挂载耗材或编辑耗材信息",
"Features are under development, please stay tuned": "功能开发中, 敬请期待", "Features are under development, please stay tuned": "功能开发中, 敬请期待",
"File bytes is empty": "获取文件内容为空,请重新发起切片操作", "File bytes is empty": "获取文件内容为空,请重新发起切片操作",
"Model": "模型",
"Coming soon...": "即将推出...",
"Search models...": "搜索模型...",
"Library": "素材库",
"Models": "模型",
"Model Detail": "模型详情",
"Published": "发布于",
"Collect": "收藏",
"Download and Open": "下载并打开",
"Print Now": "直接打印",
"direct_print_tips": "提示:直接打印仅支持云联模式",
"Case tags": "案例标签",
"Description": "描述",
"model_detail_license_title": "标准数字文件许可",
"model_detail_license_description": "该许可证限制了对数字文件或该对象的3D模型的使用、传播与商业用途,请遵守相关条款。该许可证限制了对数字文件或该对象的3D模型的使用、传播与商业用途,请遵守相关条款。",
"Weight": "重量",
"Time": "耗时",
"Layers": "层数",
"Height": "高度",
"Profiles": "打印配置",
"Image display": "图片展示",
"No models found": "未找到模型",
"Try a different search term": "请尝试其他搜索关键词",
"Print Preprocessing": "打印预处理",
"File uploaded successfully": "文件上传成功", "File uploaded successfully": "文件上传成功",
"file_uploaded_success_format": "已上传完成,将自动跳转到设备控制页面 {} 秒", "file_uploaded_success_format": "已上传完成,将自动跳转到设备控制页面 {} 秒",
"The device failed to upload the model file. Please check the device network and upload it again.": "设备上传模型文件失败,请检查设备网络并重新上传", "The device failed to upload the model file. Please check the device network and upload it again.": "设备上传模型文件失败,请检查设备网络并重新上传",
@@ -577,7 +659,7 @@
"this command requires the printer to be online, currently the printer is not online": "当前设备不在线", "this command requires the printer to be online, currently the printer is not online": "当前设备不在线",
"Bind device failed, please check if pin code is correct.": "绑定失败,可能是 PIN 码输入错误或者是 PIN 码检测失败", "Bind device failed, please check if pin code is correct.": "绑定失败,可能是 PIN 码输入错误或者是 PIN 码检测失败",
"Bind device failed, please check if device locale setting is not same as client.": "绑定失败,软件与设备区域设置不一致", "Bind device failed, please check if device locale setting is not same as client.": "绑定失败,软件与设备区域设置不一致",
"The device is in LAN mode and cannot download print files from the cloud. Please switch to Cloud Mode and try again.": "设备处于局域网模式,不支持从云端下载打印文件,请切换至云模式后重试。",
"delete_selected_files_confirm_title": "确认删除选中文件", "delete_selected_files_confirm_title": "确认删除选中文件",
"delete_selected_files_confirm_message": "确定要删除选中的文件吗?此操作无法撤销。", "delete_selected_files_confirm_message": "确定要删除选中的文件吗?此操作无法撤销。",
"clear_all_files_confirm_title": "确认清空所有文件", "clear_all_files_confirm_title": "确认清空所有文件",
@@ -603,7 +685,9 @@
"camera_auto_shutdown_message": "摄像头长时间未使用,为了节省电量,已自动关闭。", "camera_auto_shutdown_message": "摄像头长时间未使用,为了节省电量,已自动关闭。",
"got_it": "知道了", "got_it": "知道了",
"monitoring_module_hibernated": "提示:监控模块已休眠,点击播放按钮即可唤醒", "monitoring_module_hibernated": "提示:监控模块已休眠,点击播放按钮即可唤醒",
"Plates": "盘",
"Print Plates": "打印盘",
"Plate": "盘",
"Check": "查看", "Check": "查看",
"default_exception_title": "异常", "default_exception_title": "异常",
"Error Code": "错误码", "Error Code": "错误码",
@@ -1418,5 +1502,23 @@
"error_0002053200000001_title": "检测到床面可能有异物", "error_0002053200000001_title": "检测到床面可能有异物",
"error_0002053200000001_desc": "请检查并清理异物后,点击继续以恢复打印;若检查无误,也可直接点击继续。", "error_0002053200000001_desc": "请检查并清理异物后,点击继续以恢复打印;若检查无误,也可直接点击继续。",
"error_0002053200000002_title": "检测到可能存在炒面缺陷", "error_0002053200000002_title": "检测到可能存在炒面缺陷",
"error_0002053200000002_desc": "请检查模型,若缺陷可接受,点击继续以恢复打印;若检查无误,也可直接点击继续。" "error_0002053200000002_desc": "请检查模型,若缺陷可接受,点击继续以恢复打印;若检查无误,也可直接点击继续。",
"Load More": "加载更多",
"No More Data": "没有更多数据",
"Error Occurred": "发现未知错误",
"No Models Found": "未找到模型",
"Not Implemented Yet": "还未实现,请稍后再试",
"Nozzle": "喷嘴",
"Model Library":"模型库",
"Recommended for You": "官方推荐",
"units": "个",
"Total": "总量",
"Filament List": "多色耗材清单",
"Collapse": "收起",
"Expand": "展开",
"There are no available process profiles": "没有可用的打印配置",
"No split data available": "没有可用的分盘数据",
"Untitled Config": "未命名配置",
"Display Area": "展示区域",
"Next": "下一步"
} }
@@ -0,0 +1,191 @@
{
"code": 200,
"msg": "success",
"data": {
"id": 1,
"name": "test2",
"description": "",
"tag": "",
"model": "",
"checksum": "",
"pics": [
"https://picsum.photos/400/320?random=1",
"https://picsum.photos/400/320?random=2",
"https://picsum.photos/400/320?random=3",
"https://picsum.photos/400/320?random=4",
"https://picsum.photos/400/320?random=5",
"https://picsum.photos/400/320?random=6",
"https://picsum.photos/400/320?random=7"
],
"top": false,
"createDate": 1769990400000,
"modifiedDate": 1770626813247,
"publishedDate": 1770631447469,
"creator": "Snapmaker",
"creatorAvatar": "",
"copyright":
{
"id": 1,
"name": "",
"content": "",
"pic": ""
},
"totalWeight": 41.35,
"totalSecs": 15321,
"totalLayers": 336,
"maxZHeight": 27,
"filaments": [
{
"type": "PLA",
"color": "#FFFF00",
"used_g": 34.21,
"used_m": 11.47
},
{
"type": "PA",
"color": "#FF80C0",
"used_g": 4.43,
"used_m": 1.77
},
{
"type": "PLA",
"color": "#FF0000",
"used_g": 2.71,
"used_m": 0.91
}
],
"profiles": [
{
"id": 1,
"name": "",
"printerModel": "SM_U1",
"model": "https://public.resource.snapmaker.com/model/public/3mf/1/test2.3mf",
"checksum": "e56ae16cab46677cebf6631430b42e60",
"nozzle": [
0.4,
0.4,
0.4,
0.4
],
"gcode": "https://public.resource.snapmaker.com/model/public/3mf/1/test2.3mf",
"pics": [
"https://public.resource.snapmaker.com/model/public/images/1/plate_1.png",
"https://public.resource.snapmaker.com/model/public/images/1/plate_2.png"
],
"desc": "<p>&nbsp;</p><p>&nbsp;</p><p>发的师父说的</p><figure class=\"image\"><img style=\"aspect-ratio:500/657;\" src=\"https://makerworld.bblmw.cn/makerworld/model/20260202/2144736178/33355ca729f737e2.jpeg\"></figure><p><strong>sdafdsajf</strong></p><p><i>是大法师地方</i></p><p>&nbsp;</p><p><i>lll</i></p>",
"partitions": [
{
"id": 1,
"name": "aaa",
"weight": 30.27,
"secs": 10490,
"pics": [
"https://public.resource.snapmaker.com/model/public/images/1/plate_1.png",
"https://public.resource.snapmaker.com/model/public/images/1/top_1.png"
],
"colors": {
"#FFFF00": 25.85,
"#FF80C0": 4.43
},
"layerHeight": 0.2,
"wallLoops": 2,
"infillDensity": "15%",
"filaments": [
{
"type": "PLA",
"color": "#FFFF00",
"used_g": 25.85,
"used_m": 8.67
},
{
"type": "PA",
"color": "#FF80C0",
"used_g": 4.43,
"used_m": 1.77
}
]
},
{
"id": 2,
"name": "ccc",
"weight": 2.77,
"secs": 2112,
"pics": [
"https://public.resource.snapmaker.com/model/public/images/1/plate_2.png",
"https://public.resource.snapmaker.com/model/public/images/1/top_2.png"
],
"colors": {
"#FFFF00": 2.77
},
"layerHeight": 0.2,
"wallLoops": 2,
"infillDensity": "15%",
"filaments": [
{
"type": "PLA",
"color": "#FFFF00",
"used_g": 2.77,
"used_m": 0.93
}
]
},
{
"id": 3,
"name": "",
"weight": 0.18,
"secs": 44,
"pics": [
"https://public.resource.snapmaker.com/model/public/images/1/plate_3.png",
"https://public.resource.snapmaker.com/model/public/images/1/top_3.png"
],
"colors": {
"#FFFF00": 0.18
},
"layerHeight": 0.2,
"wallLoops": 2,
"infillDensity": "15%",
"filaments": [
{
"type": "PLA",
"color": "#FFFF00",
"used_g": 0.18,
"used_m": 0.06
}
]
},
{
"id": 4,
"name": "",
"weight": 8.13,
"secs": 2675,
"pics": [
"https://public.resource.snapmaker.com/model/public/images/1/plate_4.png",
"https://public.resource.snapmaker.com/model/public/images/1/top_4.png"
],
"colors": {
"#FFFF00": 5.41,
"#FF0000": 2.71
},
"layerHeight": 0.2,
"wallLoops": 2,
"infillDensity": "15%",
"filaments": [
{
"type": "PLA",
"color": "#FFFF00",
"used_g": 5.41,
"used_m": 1.81
},
{
"type": "PLA",
"color": "#FF0000",
"used_g": 2.71,
"used_m": 0.91
}
]
}
]
}
]
}
}
@@ -0,0 +1,164 @@
{
"code": 200,
"msg": "success",
"data": {
"models": [
{
"id": 1,
"name": "test2",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770631447469,
"pic": "https://public.resource.snapmaker.com/model/public/images/1/plate_1.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/1",
"top": false
},
{
"id": 12,
"name": "test-model-drag-sort",
"creator": "Snapmaker Test",
"creatorAvatar": "",
"publishedDate": 1770595200000,
"pic": "https://public.resource.snapmaker.com/model/public/images/12/1770610894993-e79cf308.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/12",
"top": false
},
{
"id": 13,
"name": "test1",
"creator": "Snapmaker",
"creatorAvatar": "https://public.resource.snapmaker.com/model/public/avatars/13/1770617029652-0c40021d.png",
"publishedDate": 1770595200000,
"pic": "https://public.resource.snapmaker.com/model/public/images/13/1770617040824-7aefaccc.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/13",
"top": false
},
{
"id": 14,
"name": "test-desc-api",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770595200000,
"pic": "https://public.resource.snapmaker.com/model/public/images/14/1770618717470-25f3b22a.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/14",
"top": false
},
{
"id": 15,
"name": "test-rich-text-model",
"creator": "TestCreator",
"creatorAvatar": "",
"publishedDate": 1770595200000,
"pic": "https://public.resource.snapmaker.com/model/public/images/15/1770622024809-940f7eb0.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/15",
"top": false
},
{
"id": 16,
"name": "lsytest001",
"creator": "Snapmaker",
"creatorAvatar": "https://public.resource.snapmaker.com/model/public/avatars/16/1770622423270-f59af51a.png",
"publishedDate": 1770595200000,
"pic": "https://public.resource.snapmaker.com/model/public/images/16/1770622433410-7822b061.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/16",
"top": false
},
{
"id": 2,
"name": "test1219",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "",
"url": "https://pre.api.snapmaker.com/api/model/detail/2",
"top": false
},
{
"id": 3,
"name": "MMM_Sloth_Multicolor_v02",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "",
"url": "https://pre.api.snapmaker.com/api/model/detail/3",
"top": false
},
{
"id": 4,
"name": "test-model-v1",
"creator": "Snapmaker Test",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "https://public.resource.snapmaker.com/model/public/images/4/profile_1770368024691/1770368024691-396d2daa.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/4",
"top": false
},
{
"id": 5,
"name": "test-model-v2",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "https://public.resource.snapmaker.com/model/public/images/5/profile_1770368609090/1770368609090-6a63b8eb.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/5",
"top": false
},
{
"id": 6,
"name": "test-model-v3",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "https://public.resource.snapmaker.com/model/public/images/6/profile_1770370083525/1770370083526-f61dbf0c.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/6",
"top": false
},
{
"id": 7,
"name": "test-model-v4",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "https://public.resource.snapmaker.com/model/public/images/7/profile_1770370889981/1770370889982-ce02ac12.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/7",
"top": false
},
{
"id": 8,
"name": "test-model-v5",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "",
"url": "https://pre.api.snapmaker.com/api/model/detail/8",
"top": false
},
{
"id": 9,
"name": "test-model-final",
"creator": "Snapmaker",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "",
"url": "https://pre.api.snapmaker.com/api/model/detail/9",
"top": false
},
{
"id": 10,
"name": "sloth-multicolor-test",
"creator": "Snapmaker Test",
"creatorAvatar": "",
"publishedDate": 1770336000000,
"pic": "https://public.resource.snapmaker.com/model/public/images/10/1770374547366-224f9f59.png",
"url": "https://pre.api.snapmaker.com/api/model/detail/10",
"top": false
}
]
},
"page": {
"page": 1,
"pageSize": 15,
"total": 27,
"next": "https://pre.api.snapmaker.com/api/model/list/page/2"
}
}
@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 5.33333V14.1333C2 14.4563 2.19419 14.7476 2.49231 14.8718L10 18M2 5.33333L9.38462 2.25641C9.77846 2.09231 10.2215 2.09231 10.6154 2.25641L14 3.66667M2 5.33333L6 7M10 8.66667V18M10 8.66667L18 5.33333M10 8.66667L6 7M10 18L17.5077 14.8718C17.8058 14.7476 18 14.4563 18 14.1333V5.33333M18 5.33333L14 3.66667M6 7L14 3.66667" stroke="#242424" stroke-width="1.4" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 497 B

@@ -75,15 +75,15 @@
"description": "", "description": "",
"series": [ "series": [
{ {
"serie": "Lite", "serie": "PolyLite",
"description": "" "description": ""
}, },
{ {
"serie": "Terra", "serie": "PolyTerra",
"description": "" "description": ""
}, },
{ {
"serie": "Sonic", "serie": "PolySonic",
"description": "" "description": ""
} }
] ]
@@ -91,12 +91,22 @@
{ {
"material": "PETG", "material": "PETG",
"description": "", "description": "",
"series": [] "series": [
{
"serie": "PolyLite",
"description": ""
}
]
}, },
{ {
"material": "ABS", "material": "ABS",
"description": "", "description": "",
"series": [] "series": [
{
"serie": "PolyLite",
"description": ""
}
]
} }
] ]
}, },
@@ -238,128 +248,132 @@
} }
], ],
"filament_special_series": [ "filament_special_series": [
"85A","90A","95A", "95A HF", "HF" "85A",
"90A",
"95A",
"95A HF",
"HF"
], ],
"filament_color": [ "filament_color": [
{ {
"name": "Ivory White", "rgba": "#FFFFFFFF",
"hex": "#FFFFFF", "name": "White",
"type": "PLA MATTE", "name_zh": "白色"
"sku": "34046"
}, },
{ {
"name": "Carbon Black", "rgba": "#E0E0E0FF",
"hex": "#000000", "name": "Light Gray",
"type": "PLA MATTE", "name_zh": "浅灰色"
"sku": "34049"
},
{
"name": "Lava Orange",
"hex": "#F78E0E",
"type": "PLA MATTE",
"sku": "34047"
},
{
"name": "Sunburst Yellow",
"hex": "#F0BE02",
"type": "PLA MATTE",
"sku": "34048"
},
{
"name": "Moss Mist",
"hex": "#BEC9A5",
"type": "PLA MATTE",
"sku": "34050"
},
{
"name": "Pine Green",
"hex": "#519F61",
"type": "PLA MATTE",
"sku": "34051"
},
{
"name": "Engine Red",
"hex": "#DE1619",
"type": "PLA MATTE",
"sku": "34052"
},
{
"name": "Ash Grey",
"hex": "#9B9EA0",
"type": "PLA MATTE",
"sku": "34053"
},
{
"name": "Celestial Blue",
"hex": "#8BD5EE",
"type": "PLA MATTE",
"sku": "34054"
},
{
"name": "Sakura Pink",
"hex": "#F3D6E5",
"type": "PLA MATTE",
"sku": "34055"
},
{
"name": "Pearl White",
"hex": "#E2DEDB",
"type": "PLA HS",
"sku": "34031"
},
{
"name": "Black",
"hex": "#080A0D",
"type": "PLA HS",
"sku": "34032"
}, },
{ {
"rgba": "#8C9099FF",
"name": "Grey", "name": "Grey",
"hex": "#8C9099", "name_zh": "灰色"
"type": "PLA HS",
"sku": "34033"
}, },
{ {
"name": "Blue", "rgba": "#000000FF",
"hex": "#003776", "name": "Black",
"type": "PLA HS", "name_zh": "黑色"
"sku": "34034"
},
{
"name": "Red",
"hex": "#E72F1D",
"type": "PLA HS",
"sku": "34035"
},
{
"name": "Purple",
"hex": "#6C5BB1",
"type": "PLA HS",
"sku": "34036"
},
{
"name": "Orange",
"hex": "#F97429",
"type": "PLA HS",
"sku": "34037"
},
{
"name": "Green",
"hex": "#2D9E59",
"type": "PLA HS",
"sku": "34038"
},
{
"name": "Yellow",
"hex": "#F4C032",
"type": "PLA HS",
"sku": "34039"
}, },
{ {
"rgba": "#6F4C2FFF",
"name": "Brown", "name": "Brown",
"hex": "#6F4C2F", "name_zh": "棕色"
"type": "PLA HS", },
"sku": "34040" {
"rgba": "#FEE5A5FF",
"name": "Butter Cream",
"name_zh": "奶油黄"
},
{
"rgba": "#F4C032FF",
"name": "Yellow",
"name_zh": "黄色"
},
{
"rgba": "#F8F81CFF",
"name": "Fluorescent Yellow",
"name_zh": "荧光黄色"
},
{
"rgba": "#BEC9A5FF",
"name": "Gray Green",
"name_zh": "灰绿色"
},
{
"rgba": "#2D9E59FF",
"name": "Green",
"name_zh": "绿色"
},
{
"rgba": "#39FF14FF",
"name": "Fluorescent Green",
"name_zh": "荧光绿色"
},
{
"rgba": "#1B5E20FF",
"name": "Dark Green",
"name_zh": "深绿色"
},
{
"rgba": "#8BD5EEFF",
"name": "Light Blue",
"name_zh": "浅蓝色"
},
{
"rgba": "#4DB6ACFF",
"name": "Teal",
"name_zh": "青绿色"
},
{
"rgba": "#1E88E5FF",
"name": "Blue",
"name_zh": "蓝色"
},
{
"rgba": "#003776FF",
"name": "Dark Blue",
"name_zh": "深蓝色"
},
{
"rgba": "#6C5BB1FF",
"name": "Blue Violet",
"name_zh": "蓝紫色"
},
{
"rgba": "#8E24AAFF",
"name": "Purple",
"name_zh": "紫色"
},
{
"rgba": "#D81B60FF",
"name": "Magenta",
"name_zh": "品红色"
},
{
"rgba": "#F55A7CFF",
"name": "Pink",
"name_zh": "粉红色"
},
{
"rgba": "#E72F1DFF",
"name": "Red",
"name_zh": "红色"
},
{
"rgba": "#F3D6E5FF",
"name": "Light Pink",
"name_zh": "浅粉色"
},
{
"rgba": "#F78E0EFF",
"name": "Orange",
"name_zh": "橙色"
},
{
"rgba": "#E65100FF",
"name": "Deep Orange",
"name_zh": "深橙色"
} }
] ]
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@@ -0,0 +1,6 @@
<svg width="51" height="18" viewBox="0 0 51 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="51" height="18" rx="3" fill="#0C63E2" fill-opacity="0.5"/>
<path d="M8.774 4.764C9.842 4.764 10.694 5.016 11.33 5.544C11.93 6.036 12.302 6.744 12.458 7.68H11.474C11.342 7.032 11.054 6.54 10.61 6.192C10.142 5.82 9.53 5.64 8.774 5.64C7.838 5.64 7.106 5.964 6.554 6.624C6.002 7.272 5.738 8.148 5.738 9.252C5.738 10.332 6.002 11.184 6.53 11.808C7.094 12.468 7.934 12.804 9.05 12.804C9.518 12.804 9.962 12.732 10.394 12.612C10.814 12.492 11.174 12.324 11.474 12.132V9.948H8.786V9.096H12.446V12.6C12.038 12.936 11.534 13.188 10.946 13.38C10.322 13.572 9.662 13.668 8.954 13.668C7.598 13.668 6.542 13.236 5.798 12.372C5.102 11.568 4.754 10.524 4.754 9.252C4.754 7.98 5.09 6.936 5.786 6.108C6.518 5.208 7.514 4.764 8.774 4.764Z" fill="#E6F2FF"/>
<path d="M22.7852 7.128C23.5532 7.128 24.1652 7.308 24.6332 7.668C25.1132 8.04 25.4012 8.604 25.5092 9.36H24.5612C24.4772 8.88 24.2852 8.52 23.9852 8.28C23.6732 8.04 23.2772 7.932 22.7852 7.932C22.1852 7.932 21.7052 8.16 21.3572 8.616C21.0212 9.06 20.8532 9.66 20.8532 10.416C20.8532 11.172 21.0092 11.772 21.3452 12.204C21.6692 12.636 22.1492 12.864 22.7732 12.864C23.8292 12.864 24.4412 12.324 24.5852 11.256H25.5452C25.4012 12.048 25.1012 12.66 24.6212 13.068C24.1532 13.464 23.5412 13.668 22.7732 13.668C21.8372 13.668 21.1172 13.356 20.6012 12.732C20.1092 12.144 19.8692 11.376 19.8692 10.428C19.8692 9.48 20.1092 8.712 20.6012 8.112C21.1292 7.452 21.8612 7.128 22.7852 7.128ZM29.4437 7.128C30.3677 7.128 31.1117 7.44 31.6637 8.088C32.1917 8.7 32.4557 9.468 32.4557 10.404C32.4557 11.328 32.1917 12.096 31.6757 12.696C31.1117 13.344 30.3677 13.668 29.4437 13.668C28.5077 13.668 27.7757 13.344 27.2237 12.696C26.6957 12.096 26.4317 11.328 26.4317 10.404C26.4317 9.468 26.6957 8.7 27.2237 8.088C27.7757 7.44 28.5077 7.128 29.4437 7.128ZM29.4437 7.932C28.7837 7.932 28.2677 8.184 27.9077 8.688C27.5717 9.144 27.4157 9.708 27.4157 10.404C27.4157 11.088 27.5717 11.652 27.9077 12.108C28.2677 12.612 28.7837 12.864 29.4437 12.864C30.0917 12.864 30.6077 12.612 30.9917 12.108C31.3157 11.652 31.4837 11.076 31.4837 10.404C31.4837 9.708 31.3157 9.144 30.9917 8.688C30.6077 8.184 30.0917 7.932 29.4437 7.932ZM38.287 4.764H39.247V13.5H38.347V12.48C37.915 13.272 37.219 13.668 36.283 13.668C35.371 13.668 34.663 13.332 34.159 12.684C33.691 12.084 33.463 11.316 33.463 10.38C33.463 9.468 33.691 8.724 34.159 8.124C34.663 7.452 35.347 7.128 36.235 7.128C37.123 7.128 37.807 7.56 38.287 8.436V4.764ZM36.439 7.92C35.755 7.92 35.239 8.16 34.903 8.64C34.591 9.06 34.447 9.636 34.447 10.38C34.447 11.124 34.591 11.712 34.891 12.144C35.227 12.624 35.731 12.876 36.403 12.876C37.027 12.876 37.507 12.636 37.855 12.156C38.155 11.724 38.311 11.16 38.311 10.464V10.344C38.311 9.624 38.131 9.036 37.795 8.592C37.447 8.136 36.991 7.92 36.439 7.92ZM43.2902 7.128C44.2862 7.128 45.0422 7.464 45.5462 8.136C46.0022 8.736 46.2422 9.588 46.2662 10.668H41.3702C41.4182 11.364 41.5982 11.904 41.9342 12.288C42.2702 12.672 42.7382 12.864 43.3262 12.864C43.8302 12.864 44.2502 12.732 44.5622 12.48C44.8262 12.264 45.0302 11.94 45.1862 11.508H46.1462C46.0022 12.108 45.7262 12.588 45.3062 12.972C44.7902 13.428 44.1302 13.668 43.3262 13.668C42.4382 13.668 41.7182 13.368 41.1902 12.792C40.6382 12.192 40.3742 11.4 40.3742 10.392C40.3742 9.48 40.6262 8.712 41.1542 8.1C41.6822 7.452 42.3902 7.128 43.2902 7.128ZM43.3142 7.932C42.7622 7.932 42.3182 8.112 41.9822 8.472C41.6462 8.832 41.4542 9.312 41.3942 9.924H45.2702C45.1502 8.592 44.4902 7.932 43.3142 7.932Z" fill="#E6F2FF"/>
<path d="M14.291 10.3047H17.2789" stroke="#E6F2FF" stroke-width="0.75"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00005 0.754578V5.03094M6.00005 5.03094L3.85303 2.88399M6.00005 5.03094L8.14695 2.88399" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 5.03094H9V7.95803L6 9.42157L3 7.95803V5.03094Z" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 9.47278C6.55228 9.47278 7 9.92049 7 10.4728C7 11.0251 6.55228 11.4728 6 11.4728C5.44772 11.4728 5 11.0251 5 10.4728C5 9.92049 5.44772 9.47278 6 9.47278Z" stroke="#4A4A4A"/>
</svg>

After

Width:  |  Height:  |  Size: 580 B

@@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.99958 9.32143V2.17857M5.99958 9.32143C5.99958 9.70031 5.84907 10.0637 5.58116 10.3316C5.31325 10.5995 4.94989 10.75 4.57101 10.75H3.14244C2.76356 10.75 2.4002 10.5995 2.13229 10.3316C1.86438 10.0637 1.71387 9.70031 1.71387 9.32143V2.17857C1.71387 1.79969 1.86438 1.43633 2.13229 1.16842C2.4002 0.90051 2.76356 0.75 3.14244 0.75H4.57101C4.94989 0.75 5.31325 0.90051 5.58116 1.16842C5.84907 1.43633 5.99958 1.79969 5.99958 2.17857M5.99958 9.32143C5.99958 9.70031 6.15009 10.0637 6.418 10.3316C6.68591 10.5995 7.04927 10.75 7.42815 10.75H8.85672C9.2356 10.75 9.59897 10.5995 9.86688 10.3316C10.1348 10.0637 10.2853 9.70031 10.2853 9.32143V2.17857C10.2853 1.79969 10.1348 1.43633 9.86688 1.16842C9.59897 0.90051 9.2356 0.75 8.85672 0.75H7.42815C7.04927 0.75 6.68591 0.90051 6.418 1.16842C6.15009 1.43633 5.99958 1.79969 5.99958 2.17857" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1014 B

@@ -0,0 +1,11 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_20117_9830)">
<path d="M5.72152 3.97058V6.15881L7.94348 6.88822" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.99902 10.749C8.76045 10.749 10.999 8.51045 10.999 5.74902C10.999 2.9876 8.76045 0.749023 5.99902 0.749023C3.2376 0.749023 0.999023 2.9876 0.999023 5.74902C0.999023 8.51045 3.2376 10.749 5.99902 10.749Z" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_20117_9830">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 649 B

@@ -0,0 +1,6 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.49995 10.8651C5.65198 10.9529 5.82445 10.9991 6 10.9991C6.17555 10.9991 6.34802 10.9529 6.50005 10.8651L10.0004 8.86487C10.1523 8.77718 10.2784 8.65109 10.3662 8.49925C10.454 8.34741 10.5003 8.17515 10.5005 7.99977V3.99928C10.5003 3.8239 10.454 3.65164 10.3662 3.4998C10.2784 3.34796 10.1523 3.22187 10.0004 3.13417L6.50005 1.13393C6.34802 1.04615 6.17555 0.999939 6 0.999939C5.82445 0.999939 5.65198 1.04615 5.49995 1.13393L1.9996 3.13417C1.84771 3.22187 1.72156 3.34796 1.63379 3.4998C1.54602 3.65164 1.49973 3.8239 1.49955 3.99928V7.99977C1.49973 8.17515 1.54602 8.34741 1.63379 8.49925C1.72156 8.65109 1.84771 8.77718 1.9996 8.86487L5.49995 10.8651Z" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 10.9999V5.99933" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.6448 3.49896L6.00024 5.99927L10.3557 3.49896" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.74955 2.13385L8.25001 4.70916" stroke="#4A4A4A" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -11,6 +11,6 @@ _flutter.buildConfig = {"engineRevision":"b8800d88be4866db1b15f8b954ab2573bba996
_flutter.loader.load({ _flutter.loader.load({
serviceWorkerSettings: { serviceWorkerSettings: {
serviceWorkerVersion: "1507942654" serviceWorkerVersion: "2549727985"
} }
}); });
@@ -3,33 +3,43 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache'; const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache'; const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {"flutter_bootstrap.js": "0a7c8042e86cd3cb2569ca1fc998cfd7", const RESOURCES = {"flutter_bootstrap.js": "5a0e89be5500761ba94ced6acbec72ce",
"version.json": "31cecd6c4768d61e6cfee5eb2d82cfa5", "version.json": "00327fe07cd4f3658188e2221ee7e70e",
"index.html": "626847718ec5c240e19799fa3d4aa135", "index.html": "2753effad264bd7ecad1460aa8a6e57f",
"/": "626847718ec5c240e19799fa3d4aa135", "/": "2753effad264bd7ecad1460aa8a6e57f",
"main.dart.js": "e3000b9495811b970bca43c9b55ce1d3", "main.dart.js": "f5fac859d7d5c10ac10ea83ef779a735",
"flutter.js": "f31737fb005cd3a3c6bd9355efd33061", "flutter.js": "f31737fb005cd3a3c6bd9355efd33061",
"version.changelog": "11ce0fece0721d2530ca068d494b62e2", "version.changelog": "342987a68a2c2325abdbf8c777f5f1f9",
"favicon.png": "be8d1ab28c20907c9869c345d0482962", "favicon.png": "be8d1ab28c20907c9869c345d0482962",
"icons/Icon-192.png": "ab1f25ced1559729e334de938eae91a5", "icons/Icon-192.png": "ab1f25ced1559729e334de938eae91a5",
"icons/Icon-maskable-192.png": "e41e8489c0f6a822acf8dab362e112b7", "icons/Icon-maskable-192.png": "e41e8489c0f6a822acf8dab362e112b7",
"icons/Icon-maskable-512.png": "4870fb6720f4fcad016cb582589d136d", "icons/Icon-maskable-512.png": "4870fb6720f4fcad016cb582589d136d",
"icons/Icon-512.png": "343022ac1c56796cb7ff635faf0646ef", "icons/Icon-512.png": "343022ac1c56796cb7ff635faf0646ef",
"manifest.json": "901d86fb8842ec0d66225a542131d689", "manifest.json": "901d86fb8842ec0d66225a542131d689",
"assets/AssetManifest.json": "20cbacdb1780e93d6fb36d8f0e179822", "assets/AssetManifest.json": "660c5d4f2ebdd5bdd856a3847ae08827",
"assets/NOTICES": "be80e66ea53f8a268fb7677969331e98", "assets/NOTICES": "d069331fe8f2999724abec0a42604cc7",
"assets/FontManifest.json": "0dc3d44d47c5e2636cdca4babafb2396", "assets/FontManifest.json": "0dc3d44d47c5e2636cdca4babafb2396",
"assets/AssetManifest.bin.json": "dc681d818e8a6e5fc76e929915894fa3", "assets/AssetManifest.bin.json": "fc74193f1dcf1df8092bf993cb281f41",
"assets/packages/lava_device_control/assets/files/filament.json": "e78d490824eb52bf5fd3adcb07296cb0", "assets/packages/lava_model_station/assets/404.png": "e8a45c994c2f6f551cf1e052f64dba1c",
"assets/packages/lava_model_station/assets/placehoder-image.png": "fd0b3547e2b90c124112401d7a8f6a02",
"assets/packages/lava_model_station/assets/svgs/iconPlate.svg": "05acab23d908534f0660b4f06cc36e8d",
"assets/packages/lava_model_station/assets/svgs/iconTime.svg": "bcddee2512587e441a39fbdf4b0c1d70",
"assets/packages/lava_model_station/assets/svgs/iconGcode.svg": "9b20a88a3f79c7347e665a8738eeea6b",
"assets/packages/lava_model_station/assets/svgs/iconNozzle.svg": "fa46a390b0c9db063e289af55b55329e",
"assets/packages/lava_model_station/assets/svgs/iconWeight.svg": "bfe3246df9e15f08bc5208d34ae3a814",
"assets/packages/lava_model_station/assets/empty-box.png": "f0fcb1ead826eec9ff565c2ac0dfd1da",
"assets/packages/lava_device_control/assets/files/filament.json": "f8f71f02e204c523b70e982d281f407d",
"assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "391ff5f9f24097f4f6e4406690a06243", "assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "391ff5f9f24097f4f6e4406690a06243",
"assets/packages/fluttertoast/assets/toastify.js": "56e2c9cedd97f10e7e5f1cebd85d53e3", "assets/packages/fluttertoast/assets/toastify.js": "56e2c9cedd97f10e7e5f1cebd85d53e3",
"assets/packages/fluttertoast/assets/toastify.css": "a85675050054f179444bc5ad70ffc635", "assets/packages/fluttertoast/assets/toastify.css": "a85675050054f179444bc5ad70ffc635",
"assets/packages/wakelock_plus/assets/no_sleep.js": "7748a45cd593f33280669b29c2c8919a", "assets/packages/wakelock_plus/assets/no_sleep.js": "7748a45cd593f33280669b29c2c8919a",
"assets/shaders/ink_sparkle.frag": "ecc85a2e95f5e9f53123dcaf8cb9b6ce", "assets/shaders/ink_sparkle.frag": "ecc85a2e95f5e9f53123dcaf8cb9b6ce",
"assets/AssetManifest.bin": "26378ad7581792ebae1a2fa9dfd66b24", "assets/AssetManifest.bin": "037b9d5cbc8fd045ebdd1277e9b52873",
"assets/fonts/MaterialIcons-Regular.otf": "694c59090b9196ac07e9b4e8368882f8", "assets/fonts/MaterialIcons-Regular.otf": "165e0312a2248a5fe696bc445a5c4e9a",
"assets/assets/i10n/zh-CN.json": "131f0702824659846d2b184440c5003a", "assets/assets/mock_data/model_detail.json": "9c3ce3e4bb3dbe78b74fc2d11d152485",
"assets/assets/i10n/en.json": "a03ae4b5f57a2978e69066b032f4aded", "assets/assets/mock_data/model_station_list.json": "1dcbd846d8b97bae543caba6ce135040",
"assets/assets/i10n/zh-CN.json": "b75b9cdfaa7f0dd8685f451947e720b9",
"assets/assets/i10n/en.json": "49a88f8ed551553ed2720b40174f76a7",
"assets/assets/images/deviceNoResponse.webp": "1ca23a7feedfdc34362ea5789ccf895b", "assets/assets/images/deviceNoResponse.webp": "1ca23a7feedfdc34362ea5789ccf895b",
"assets/assets/images/deviceAuthorized.webp": "8eb814193bed15cec22658018871aba8", "assets/assets/images/deviceAuthorized.webp": "8eb814193bed15cec22658018871aba8",
"assets/assets/images/IpInputGuide.webp": "06c11ce1dadc2910676aec6d40a5eea5", "assets/assets/images/IpInputGuide.webp": "06c11ce1dadc2910676aec6d40a5eea5",
@@ -52,6 +62,7 @@ const RESOURCES = {"flutter_bootstrap.js": "0a7c8042e86cd3cb2569ca1fc998cfd7",
"assets/assets/images/deviceInvalidVersion.webp": "66e3b61ac908b900761bf014e92c1d3d", "assets/assets/images/deviceInvalidVersion.webp": "66e3b61ac908b900761bf014e92c1d3d",
"assets/assets/svgs/iconMainCooling.svg": "55b38461348e477abac33fdda8f98e32", "assets/assets/svgs/iconMainCooling.svg": "55b38461348e477abac33fdda8f98e32",
"assets/assets/svgs/iconCloseWhite.svg": "21e00e7b7a7031241d82983eef24b416", "assets/assets/svgs/iconCloseWhite.svg": "21e00e7b7a7031241d82983eef24b416",
"assets/assets/svgs/modelStation.svg": "dfd8ccb848b39df409d943c048c36a84",
"assets/assets/svgs/iconClose.svg": "f6db4c0e4369cc05ae28d3bea8d5b1ad", "assets/assets/svgs/iconClose.svg": "f6db4c0e4369cc05ae28d3bea8d5b1ad",
"assets/assets/svgs/loginPlatformApple.svg": "be43d78435feca50bbabad292a1039c7", "assets/assets/svgs/loginPlatformApple.svg": "be43d78435feca50bbabad292a1039c7",
"assets/assets/svgs/iconNotific.svg": "27082276596d830c36e1f5d0902b3929", "assets/assets/svgs/iconNotific.svg": "27082276596d830c36e1f5d0902b3929",
File diff suppressed because one or more lines are too long
+9 -18
View File
@@ -1,20 +1,11 @@
# Changelog
# 更新日志 / Changelog ## 2026-03-11
## [版本号] 2026年1月30日 ### Features
- Feature:
### 修复 / Fixed * Added a new model entry
- **预打印页面设备耗材信息获取失败** * Support more official consumables
Fixed device consumable information retrieval failure on pre-print page * Add new types of consumables color editing
- 问题描述:在预打印页面无法正确获取到设备的耗材状态信息 ### Bugfix
- Issue: Device consumable status could not be retrieved correctly on the pre-print page * Fixed the issue where devices would automatically disconnect when connected over Lan network
- **附近设备连接功能异常**
Fixed nearby device connection failure
- 问题描述:通过"附近设备"功能搜索到的设备无法正常连接
- Issue: Devices discovered via "Nearby Devices" feature could not be connected
- **局域网设备文件传输失败**
Fixed file transfer failure for LAN-only devices
- 问题描述:仅支持局域网连接的设备通过IP地址连接后,无法发起文件传输
- Issue: File transfer could not be initiated for LAN-only devices after connecting via IP address
+1 -1
View File
@@ -1 +1 @@
{"app_name":"orca","version":"2.2.4","build_number":"20260205155654","package_name":"orca"} {"app_name":"orca","version":"2.3.2","build_number":"20260324145420","package_name":"orca"}
+13
View File
@@ -2,7 +2,20 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<!-- Allow dynamic loading of libraries without signature validation. Used for 3dconnection drivers. -->
<key>com.apple.security.cs.disable-library-validation</key> <key>com.apple.security.cs.disable-library-validation</key>
<true/> <true/>
<!-- Allow JIT compilation (required for JavaScript/WKWebView on macOS) -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<!-- Allow unsigned executable memory (required for JIT/WKWebView) -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<!-- Allow DYLD environment variables -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<!-- Explicitly disable App Sandbox (this app needs full filesystem/network access) -->
<key>com.apple.security.app-sandbox</key>
<false/>
</dict> </dict>
</plist> </plist>
@@ -38,9 +38,9 @@
<color type="primary" scheme_preference="light">#009688</color> <color type="primary" scheme_preference="light">#009688</color>
</branding> </branding>
<releases> <releases>
<release version="2.2.1" date="2025-12-26"> <release version="2.3.1" date="2026-3-24">
<description> <description>
<p>Version 2.2.1 release with improvements and bug fixes.</p> <p>Version 2.3.1 release with improvements and bug fixes.</p>
</description> </description>
</release> </release>
</releases> </releases>
@@ -104,12 +104,6 @@ modules:
url: https://github.com/SoftFever/Orca-deps-wxWidgets url: https://github.com/SoftFever/Orca-deps-wxWidgets
branch: master branch: master
path: ../ path: ../
- type: file
path: patches/0001-Enable-using-a-dark-theme-when-Gnome-dark-style-is-s.patch
dest-filename: wxwidgets-dark-theme.patch
- type: shell
commands:
- patch -p1 < wxwidgets-dark-theme.patch
cleanup: cleanup:
- "*.la" - "*.la"
- "*.a" - "*.a"
+466
View File
@@ -0,0 +1,466 @@
#!/bin/bash
# macOS 应用签名、打包、公证完整流程脚本
# 用法: ./scripts/sign_and_package.sh [arm64|x86_64] [app_path]
set -e
# 检测架构参数
ARCH="${1:-$(uname -m)}"
# 标准化架构名称
case "$ARCH" in
arm64|aarch64)
ARCH="arm64"
;;
x86_64|x86-64|amd64)
ARCH="x86_64"
;;
*)
echo "错误: 不支持的架构 $ARCH"
echo "用法: $0 [arm64|x86_64] [app_path]"
exit 1
;;
esac
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$PROJECT_DIR/build/$ARCH"
APP_NAME="Snapmaker_Orca"
APP_NAME_EX="Snapmaker Orca"
DMG_NAME="Snapmaker_Orca_${ARCH}.dmg"
# 证书配置
CERTIFICATE_ID="Developer ID Application: Shenzhen Snapmaker Technologies Co., Ltd. (5NGD3B3V37)"
ENTITLEMENTS="$PROJECT_DIR/scripts/disable_validation.entitlements"
# ============================================
# 公证凭据配置(已设置)
# ============================================
NOTARY_APPLE_ID="snapmaker-app@snapmaker.com"
NOTARY_TEAM_ID="5NGD3B3V37"
#NOTARY_KEYCHAIN_PROFILE="snapmaker"
NOTARY_PASSWORD="guhi-nuxy-mgnh-cbqs"
echo "=========================================="
echo "macOS 应用签名、打包、公证完整流程"
echo "=========================================="
echo "架构: $ARCH"
echo "证书: $CERTIFICATE_ID"
echo "TEAM_ID: 5NGD3B3V37"
echo "项目目录: $PROJECT_DIR"
echo
# ============================================
# 查找应用
# ============================================
# 如果提供了 app 路径
if [ -n "$2" ]; then
SOURCE_APP="$2"
if [ ! -d "$SOURCE_APP" ]; then
echo "错误: 找不到应用: $SOURCE_APP"
exit 1
fi
echo "使用指定应用: $SOURCE_APP"
else
# 自动查找编译好的 app
for possible_path in \
"$BUILD_DIR/src/Release/$APP_NAME.app" \
"$BUILD_DIR/src/RelWithDebInfo/$APP_NAME.app" \
"$BUILD_DIR/src/$APP_NAME.app" \
"$BUILD_DIR/src/Debug/$APP_NAME.app"
do
if [ -d "$possible_path" ]; then
SOURCE_APP="$possible_path"
echo "找到应用: $SOURCE_APP"
break
fi
done
# 检查空格版本名称
if [ -z "$SOURCE_APP" ]; then
for possible_path in \
"$BUILD_DIR/src/Release/Snapmaker Orca.app" \
"$BUILD_DIR/Snapmaker_Orca/Snapmaker Orca.app"
do
if [ -d "$possible_path" ]; then
SOURCE_APP="$possible_path"
echo "找到应用: $SOURCE_APP"
break
fi
done
fi
if [ -z "$SOURCE_APP" ]; then
echo "错误: 在 $BUILD_DIR 中找不到编译好的 $APP_NAME.app"
echo "请先编译 $ARCH 版本: ./build_release_macos.sh -s -a $ARCH"
exit 1
fi
fi
# 创建临时工作目录
WORK_DIR="$BUILD_DIR/sign_package"
STAGING_DIR="$WORK_DIR/staging"
rm -rf "$WORK_DIR"
mkdir -p "$STAGING_DIR"
# 清理所有可能的残留挂载点(在开始工作前)
echo "清理可能的残留挂载点..."
for mount_point in /Volumes/Snapmaker* /Volumes/Snapmaker*; do
if [ -d "$mount_point" ]; then
echo " 卸载: $mount_point"
hdiutil detach "$mount_point" -force 2>/dev/null || true
fi
done
sleep 1
# 复制应用到工作目录
echo
echo "=========================================="
echo "步骤 1/6: 复制应用"
echo "=========================================="
echo "复制应用到工作目录..."
cp -R "$SOURCE_APP" "$STAGING_DIR/$APP_NAME.app"
FINAL_APP="$STAGING_DIR/$APP_NAME.app"
# 删除 .DS_Store 文件
find "$FINAL_APP" -name '.DS_Store' -delete
# 删除 PkgInfo 文件(冗余文件)
rm -f "$FINAL_APP/Contents/PkgInfo" 2>/dev/null || true
# 清理所有扩展属性(包括 com.apple.quarantine),避免 Gatekeeper 问题
echo "清理扩展属性..."
xattr -cr "$FINAL_APP"
# ============================================
# 打包外部依赖库
# ============================================
APP_MACOS_DIR="$FINAL_APP/Contents/MacOS"
APP_FRAMEWORKS_DIR="$FINAL_APP/Contents/Frameworks"
EXECUTABLE="$APP_MACOS_DIR/$APP_NAME"
# 确保 Frameworks 目录存在
mkdir -p "$APP_FRAMEWORKS_DIR"
echo
echo "检查并打包外部依赖库..."
# 查找所有外部依赖(非系统库)
EXTERNAL_LIBS=$(otool -L "$EXECUTABLE" | grep -E "opt/homebrew|usr/local|opt/local" | awk '{print $1}')
if [ -n "$EXTERNAL_LIBS" ]; then
echo "发现外部依赖:"
echo "$EXTERNAL_LIBS"
echo
for LIB_PATH in $EXTERNAL_LIBS; do
if [ -f "$LIB_PATH" ]; then
LIB_NAME=$(basename "$LIB_PATH")
echo "处理: $LIB_NAME"
# 获取实际的库文件路径(处理符号链接)- macOS 兼容方式
if command -v realpath &> /dev/null; then
REAL_LIB=$(realpath "$LIB_PATH" 2>/dev/null || echo "$LIB_PATH")
else
# macOS 不支持 realpath/readlink -f,使用 perl
REAL_LIB=$(perl -MCwd=abs_path -e 'print abs_path(shift)' "$LIB_PATH" 2>/dev/null || echo "$LIB_PATH")
fi
REAL_NAME=$(basename "$REAL_LIB")
# 复制实际的库文件
if [ ! -f "$APP_FRAMEWORKS_DIR/$REAL_NAME" ]; then
cp "$REAL_LIB" "$APP_FRAMEWORKS_DIR/$REAL_NAME"
# 修改库的 ID 为文件名(不带路径)
install_name_tool -id "$REAL_NAME" "$APP_FRAMEWORKS_DIR/$REAL_NAME"
# 删除库中的 rpath(避免问题)
install_name_tool -delete_rpath "@loader_path/../lib" "$APP_FRAMEWORKS_DIR/$REAL_NAME" 2>/dev/null || true
install_name_tool -delete_rpath "@loader_path/lib" "$APP_FRAMEWORKS_DIR/$REAL_NAME" 2>/dev/null || true
fi
# 注释掉:不创建中间符号链接,避免冗余
# if [ "$LIB_NAME" != "$REAL_NAME" ]; then
# (cd "$APP_FRAMEWORKS_DIR" && ln -sf "$REAL_NAME" "$LIB_NAME")
# fi
# 更新可执行文件中的依赖引用
install_name_tool -change "$LIB_PATH" "@executable_path/../Frameworks/$REAL_NAME" "$EXECUTABLE" 2>/dev/null || true
install_name_tool -change "$REAL_LIB" "@executable_path/../Frameworks/$REAL_NAME" "$EXECUTABLE" 2>/dev/null || true
fi
done
echo
echo "已打包的依赖库:"
ls -la "$APP_FRAMEWORKS_DIR/"
else
echo "没有外部依赖需要处理"
fi
# 移除不需要的 rpath
echo
echo "清理 rpath..."
install_name_tool -delete_rpath "/opt/homebrew/lib" "$EXECUTABLE" 2>/dev/null || true
install_name_tool -delete_rpath "/usr/local/lib" "$EXECUTABLE" 2>/dev/null || true
install_name_tool -delete_rpath "/opt/local/lib" "$EXECUTABLE" 2>/dev/null || true
# 修复 Resources 符号链接 (如果是符号链接)
RESOURCES_LINK="$FINAL_APP/Contents/Resources"
if [ -L "$RESOURCES_LINK" ]; then
echo "修复 Resources 符号链接..."
RESOURCES_TARGET=$(readlink "$RESOURCES_LINK")
rm "$RESOURCES_LINK"
cp -R "$RESOURCES_TARGET" "$RESOURCES_LINK"
fi
# 验证依赖
echo
echo "验证最终依赖:"
otool -L "$EXECUTABLE" | grep -E "@executable|libzstd|libsentry" || echo "无特殊依赖"
# ============================================
# 步骤 2/6: 签名应用
# ============================================
echo
echo "=========================================="
echo "步骤 2/6: 签名应用"
echo "=========================================="
APP_FRAMEWORKS_DIR="$FINAL_APP/Contents/Frameworks"
APP_MACOS_DIR="$FINAL_APP/Contents/MacOS"
EXECUTABLE="$APP_MACOS_DIR/$APP_NAME"
# 2.1 移除现有签名
echo "2.1 移除现有签名..."
codesign --remove-signature "$FINAL_APP" 2>/dev/null || true
# 2.2 签名 Frameworks 和动态库(使用 runtime 选项)
echo "2.2 签名 Frameworks 和动态库(使用 runtime 选项)..."
if [ -d "$APP_FRAMEWORKS_DIR" ]; then
# 签名所有 .framework
for framework in "$APP_FRAMEWORKS_DIR"/*.framework; do
if [ -d "$framework" ]; then
echo " - 签名: $(basename "$framework")"
codesign --force --verbose --options runtime --timestamp --sign "$CERTIFICATE_ID" "$framework" 2>/dev/null || true
fi
done
# 签名所有 .dylib
for dylib in "$APP_FRAMEWORKS_DIR"/*.dylib; do
if [ -f "$dylib" ]; then
echo " - 签名: $(basename "$dylib")"
codesign --force --verbose --options runtime --timestamp --sign "$CERTIFICATE_ID" "$dylib"
fi
done
# 签名其他可能存在的库文件(如 .so)
for lib in "$APP_FRAMEWORKS_DIR"/*.*; do
if [ -f "$lib" ]; then
case "$lib" in
*.dylib) ;; # 已处理,跳过
*)
echo " - 签名: $(basename "$lib")"
codesign --force --verbose --options runtime --timestamp --sign "$CERTIFICATE_ID" "$lib"
;;
esac
fi
done
fi
# 2.3 签名辅助工具
echo "2.3 签名辅助工具(使用 runtime 选项)..."
if [ -f "$APP_MACOS_DIR/crashpad_handler" ]; then
echo " - 签名: crashpad_handler"
codesign --force --verbose --options runtime --timestamp --sign "$CERTIFICATE_ID" "$APP_MACOS_DIR/crashpad_handler"
fi
# 2.4 签名整个 app bundle(应用 entitlements
echo "2.4 签名整个 app bundle(应用 entitlements..."
echo " 这会签名所有组件并将 entitlements 应用到主可执行文件"
codesign --force --verbose --options runtime --timestamp \
--entitlements "$ENTITLEMENTS" \
--sign "$CERTIFICATE_ID" \
"$FINAL_APP"
# 2.5 验证签名和 entitlements
echo "2.5 验证签名和 entitlements..."
echo " 检查签名..."
codesign -vvv "$FINAL_APP" 2>&1 | grep -E "valid on disk|Authority|TeamIdentifier" | head -5
echo ""
echo " 检查 entitlements..."
if codesign -d --entitlements - "$FINAL_APP" 2>&1 | grep -q "com.apple.security.cs.disable-library-validation"; then
echo " ✓ Entitlements 正确嵌入!"
else
echo "警告: 预期的 entitlements 未找到"
fi
# ============================================
# 步骤 3/6: 创建并签名 DMG
# 流程与 GitHub Actions 完全一致:准备内容 -> 一步 create UDZO(不挂载)-> 签名 DMG
# 不挂载可避免本地「操作不被允许」;打开 DMG 后为系统默认图标布局
# ============================================
echo
echo "=========================================="
echo "步骤 3/6: 创建并签名 DMG"
echo "=========================================="
DMG_CONTENT_DIR="$WORK_DIR/dmg_content"
rm -rf "$DMG_CONTENT_DIR"
mkdir -p "$DMG_CONTENT_DIR"
rm -rf "$DMG_CONTENT_DIR/.fseventsd" 2>/dev/null || true
# 复制应用(显示名 Snapmaker Orca.app)并创建 Applications 符号链接(与 CI 一致)
echo "准备 DMG 内容..."
cp -R "$FINAL_APP" "$DMG_CONTENT_DIR/$APP_NAME_EX.app"
# 清理 DMG 内容中的扩展属性(重要!避免 Gatekeeper 问题)
xattr -cr "$DMG_CONTENT_DIR/$APP_NAME_EX.app"
ln -sfn /Applications "$DMG_CONTENT_DIR/Applications"
# 卷名不使用下划线,避免 macOS 安全机制阻止
DMG_VOLNAME="Snapmaker_Orca"
FINAL_DMG_PATH="$BUILD_DIR/$DMG_NAME"
rm -f "$FINAL_DMG_PATH"
# 再次清理可能残留的挂载点
if [ -d "/Volumes/$DMG_VOLNAME" ]; then
echo "检测到残留挂载点 /Volumes/$DMG_VOLNAME,正在强制卸载..."
hdiutil detach "/Volumes/$DMG_VOLNAME" -force 2>/dev/null || true
sleep 2
fi
# 检查是否有同名 DMG 已挂载
MOUNTED_DMG=$(hdiutil info | grep "/Volumes/$DMG_VOLNAME" || true)
if [ -n "$MOUNTED_DMG" ]; then
echo "警告: 发现已挂载的同名卷,尝试卸载..."
hdiutil info | grep "/Volumes/$DMG_VOLNAME" | grep -o '/dev/disk[0-9]*' | while read -r disk; do
hdiutil detach "$disk" -force 2>/dev/null || true
done
sleep 2
fi
echo "创建 DMG: $FINAL_DMG_PATH (卷名: $DMG_VOLNAME)"
if ! hdiutil create \
-volname "$DMG_VOLNAME" \
-srcfolder "$DMG_CONTENT_DIR" \
-ov \
-format UDZO \
-imagekey zlib-level=9 \
-o "$FINAL_DMG_PATH"; then
echo ""
echo "错误: hdiutil create 失败"
echo "尝试使用替代方法创建 DMG..."
# 备用方案:使用 mktemp 创建临时卷名
TEMP_VOLNAME="Snapmaker_Orca_$$"
if hdiutil create \
-volname "$TEMP_VOLNAME" \
-srcfolder "$DMG_CONTENT_DIR" \
-ov \
-format UDZO \
-imagekey zlib-level=9 \
-o "$FINAL_DMG_PATH"; then
echo "使用临时卷名创建成功"
else
echo "错误: DMG 创建失败,请手动检查 /Volumes 目录"
echo "运行 'ls -la /Volumes/' 查看挂载点"
echo "运行 'hdiutil info' 查看所有挂载的磁盘镜像"
exit 1
fi
fi
[ ! -f "$FINAL_DMG_PATH" ] && echo "错误: 未生成 DMG" && exit 1
# 签名 DMG
echo "签名 DMG..."
codesign --force --timestamp --sign "$CERTIFICATE_ID" "$FINAL_DMG_PATH"
echo "验证 DMG 签名..."
codesign -vvv "$FINAL_DMG_PATH" 2>&1 | head -3
rm -rf "$DMG_CONTENT_DIR"
echo ""
echo "=========================================="
echo "DMG 创建和签名完成!"
echo "=========================================="
echo "DMG: $FINAL_DMG_PATH"
echo "大小: $(du -h "$FINAL_DMG_PATH" | cut -f1)"
# ============================================
# 步骤 4/6: 公证 DMG
# ============================================
echo ""
echo "=========================================="
echo "步骤 4/6: 公证 DMG"
echo "=========================================="
# 判断是否可公证:检查密码是否已设置
echo "检查公证凭据..."
echo " Apple ID: $NOTARY_APPLE_ID"
echo " Team ID: $NOTARY_TEAM_ID"
if [ -z "$NOTARY_PASSWORD" ] || [ "$NOTARY_PASSWORD" = "__PLEASE_ENTER_PASSWORD__" ]; then
echo ""
echo "密码未设置!"
echo ""
echo "请在脚本中设置密码:"
echo " NOTARY_PASSWORD=\"your-app-specific-password\""
echo ""
echo "或者通过环境变量设置:"
echo " export NOTARY_PASSWORD=\"your-app-specific-password\""
echo ""
echo "跳过公证步骤..."
else
echo "✓ 密码已配置"
echo ""
echo "=========================================="
echo "步骤 5/6: 提交公证"
echo "=========================================="
echo "提交 DMG 到 Apple 公证服务..."
xcrun notarytool submit "$FINAL_DMG_PATH" \
--apple-id "$NOTARY_APPLE_ID" \
--team-id "$NOTARY_TEAM_ID" \
--password "$NOTARY_PASSWORD" \
--wait \
--progress
echo ""
echo "=========================================="
echo "步骤 6/6: 装订公证票据"
echo "=========================================="
echo "装订公证票据到 DMG..."
xcrun stapler staple "$FINAL_DMG_PATH"
# 验证公证结果
echo ""
echo "验证公证结果..."
xcrun stapler validate -v "$FINAL_DMG_PATH"
echo ""
echo "=========================================="
echo "公证完成!"
echo "=========================================="
echo "此 DMG 已签名并公证,可以在任何 Mac 上无缝运行"
fi
echo ""
echo "=========================================="
echo "完成!"
echo "=========================================="
echo "架构: $ARCH"
echo "应用: $FINAL_APP"
echo "DMG: $FINAL_DMG_PATH"
echo "证书: $CERTIFICATE_ID"
echo "TEAM_ID: 5NGD3B3V37"
echo ""
echo "使用方法:"
echo " 1. 打开 DMG: open $FINAL_DMG_PATH"
echo " 2. 将 $APP_NAME_EX.app 拖拽到 Applications 文件夹"
echo " 3. 从 Applications 运行应用"
echo "=========================================="
+1
View File
@@ -140,6 +140,7 @@ if (APPLE)
# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
# -liconv: boost links to libiconv by default # -liconv: boost links to libiconv by default
target_link_libraries(Snapmaker_Orca "-liconv -framework IOKit" "-framework CoreFoundation" "-framework AVFoundation" "-framework AVKit" "-framework CoreMedia" "-framework VideoToolbox" -lc++) target_link_libraries(Snapmaker_Orca "-liconv -framework IOKit" "-framework CoreFoundation" "-framework AVFoundation" "-framework AVKit" "-framework CoreMedia" "-framework VideoToolbox" -lc++)
elseif (MSVC) elseif (MSVC)
# Manifest is provided through Snapmaker_Orca.rc, don't generate your own. # Manifest is provided through Snapmaker_Orca.rc, don't generate your own.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
+38
View File
@@ -71,6 +71,44 @@ namespace common
#endif // _WIN32 #endif // _WIN32
return machineId; return machineId;
} }
std::string get_profile_version()
{
std::string versionFilePath = "";
#ifdef _WIN32
PWSTR pszPath = nullptr;
char* path = new char[MAX_PATH]();
size_t pathLength = 0;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pszPath);
if (SUCCEEDED(hr)) {
wcstombs_s(&pathLength, path, MAX_PATH, pszPath, MAX_PATH);
CoTaskMemFree(pszPath);
}
std::string filePath = path;
versionFilePath = filePath + "\\" + std::string("Snapmaker_Orca\\system\\Snapmaker.json");
delete[] path;
#elif __APPLE__
const char* home_env = getenv("HOME");
versionFilePath = home_env;
versionFilePath = versionFilePath + "/Library/Application Support/Snapmaker_Orca/system/Snapmaker.json";
#else
#endif
std::ifstream json_file(versionFilePath);
if (!json_file.is_open()) {
std::ifstream json_file(versionFilePath);
return "";
}
nlohmann::json json_data;
json_file >> json_data;
std::string str_version = json_data.value("version", "");
return str_version;
}
std::string get_flutter_version() std::string get_flutter_version()
{ {
+4 -2
View File
@@ -6,12 +6,12 @@
#define SLIC3R_APP_NAME "Snapmaker Orca" #define SLIC3R_APP_NAME "Snapmaker Orca"
#define SLIC3R_APP_KEY "Snapmaker_Orca" #define SLIC3R_APP_KEY "Snapmaker_Orca"
#define SLIC3R_VERSION "01.10.01.50" #define SLIC3R_VERSION "01.10.01.50"
#define Snapmaker_VERSION "2.2.4" #define Snapmaker_VERSION "2.3.1"
#define MIN_FIRM_VER "1.0.0" #define MIN_FIRM_VER "1.0.0"
#ifndef GIT_COMMIT_HASH #ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized #define GIT_COMMIT_HASH "0000000" // 0000000 means uninitialized
#endif #endif
#define SLIC3R_BUILD_ID "2.2.4" #define SLIC3R_BUILD_ID "2.3.1"
// #define SLIC3R_RC_VERSION "01.10.01.50" // #define SLIC3R_RC_VERSION "01.10.01.50"
#define BBL_RELEASE_TO_PUBLIC 1 #define BBL_RELEASE_TO_PUBLIC 1
#define BBL_INTERNAL_TESTING 0 #define BBL_INTERNAL_TESTING 0
@@ -23,6 +23,8 @@ namespace common
std::string get_flutter_version(); std::string get_flutter_version();
std::string get_profile_version();
std::string getMachineId(); std::string getMachineId();
std::string getLocalArea(); std::string getLocalArea();
+37
View File
@@ -130,10 +130,47 @@
<string>NSApplication</string> <string>NSApplication</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
<key>NSLocalNetworkUsageDescription</key>
<string>Snapmaker Orca needs access to your local network to discover and connect to your 3D printer(s), and to serve the app's device interface on localhost.</string>
<key>NSBonjourServices</key>
<array>
<string>_http._tcp</string>
<string>_printer._tcp</string>
</array>
<key>LSEnvironment</key> <key>LSEnvironment</key>
<dict> <dict>
<key>ASAN_OPTIONS</key> <key>ASAN_OPTIONS</key>
<string>detect_container_overflow=0</string> <string>detect_container_overflow=0</string>
</dict> </dict>
<!-- Allow HTTP connections to localhost for local web server (required for newer macOS versions) -->
<key>NSAppTransportSecurity</key>
<dict>
<!-- Completely disable ATS for localhost (allows HTTP instead of HTTPS) -->
<key>NSAllowsLocalNetworking</key>
<true/>
<!-- Allow arbitrary loads for local development server -->
<key>NSAllowsArbitraryLoads</key>
<false/>
<!-- Specific exception for localhost connections -->
<key>NSExceptionDomains</key>
<dict>
<!-- Allow HTTP (not HTTPS) for localhost -->
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<!-- Allow HTTP for 127.0.0.1 -->
<key>127.0.0.1</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<false/>
</dict>
</dict>
</dict>
</dict> </dict>
</plist> </plist>
+11 -11
View File
@@ -2156,19 +2156,19 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
// Invalid first layer index, don't print brim // Invalid first layer index, don't print brim
loops_num = 0; loops_num = 0;
} else { } else {
// Stop print chamfer if depth changes // Stop print chamfer if depth changes
bool depth_changed = (m_layer_info->depth != m_plan[m_first_layer_idx].depth); bool depth_changed = (m_layer_info->depth != m_plan[m_first_layer_idx].depth);
if (depth_changed) { if (depth_changed) {
loops_num = 0;
} else {
// Limit max chamfer width to configured value
int chamfer_loops_num = (int) (m_prime_tower_brim_chamfer_max_width / spacing);
loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st;
// Ensure loops_num doesn't go negative
if (loops_num < 0)
loops_num = 0; loops_num = 0;
} else {
// Limit max chamfer width to configured value
int chamfer_loops_num = (int) (m_prime_tower_brim_chamfer_max_width / spacing);
loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st;
// Ensure loops_num doesn't go negative
if (loops_num < 0)
loops_num = 0;
}
} }
}
} else { } else {
// Non-first layer + chamfer disabled: don't print brim (revert to original behavior) // Non-first layer + chamfer disabled: don't print brim (revert to original behavior)
loops_num = 0; loops_num = 0;
+4 -2
View File
@@ -504,8 +504,10 @@ set(SLIC3R_GUI_SOURCES
GUI/SMPhysicalPrinterDialog.cpp GUI/SMPhysicalPrinterDialog.cpp
GUI/SSWCP.cpp GUI/SSWCP.cpp
GUI/SSWCP.hpp GUI/SSWCP.hpp
GUI/WCPDownloadManager.cpp GUI/DownloadManager.cpp
GUI/WCPDownloadManager.hpp GUI/DownloadManager.hpp
GUI/GenericDownloadDialog.cpp
GUI/GenericDownloadDialog.hpp
GUI/WebPresetDialog.hpp GUI/WebPresetDialog.hpp
GUI/WebPresetDialog.cpp GUI/WebPresetDialog.cpp
GUI/WebSMUserLoginDialog.cpp GUI/WebSMUserLoginDialog.cpp
+1
View File
@@ -57,6 +57,7 @@ BBLStatusBarSend::BBLStatusBarSend(wxWindow *parent, int id)
m_cancelbutton->SetBorderColor(btn_bd_white); m_cancelbutton->SetBorderColor(btn_bd_white);
m_cancelbutton->SetTextColor(btn_txt_white); m_cancelbutton->SetTextColor(btn_txt_white);
m_cancelbutton->SetCornerRadius(m_self->FromDIP(12)); m_cancelbutton->SetCornerRadius(m_self->FromDIP(12));
m_cancelbutton->SetCursor(wxCURSOR_HAND);
m_cancelbutton->Bind(wxEVT_BUTTON, m_cancelbutton->Bind(wxEVT_BUTTON,
[this](wxCommandEvent &evt) { [this](wxCommandEvent &evt) {
m_was_cancelled = true; m_was_cancelled = true;
+579
View File
@@ -0,0 +1,579 @@
#include "DownloadManager.hpp"
#include "GUI_App.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <vector>
#include <ctime>
namespace Slic3r { namespace GUI {
// ============================================================================
// Helper Functions
// ============================================================================
std::string DownloadManager::get_unique_file_path(const boost::filesystem::path& file_path)
{
// file_path should be the complete absolute path: directory + filename
std::string original_path = file_path.string();
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Checking path '%1%'") % original_path;
// Check if file exists, if not return original path
if (!boost::filesystem::exists(file_path)) {
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: File does not exist, returning original path '%1%'") % original_path;
return original_path;
}
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: File exists, generating unique name");
boost::filesystem::path parent_dir = file_path.parent_path();
std::string filename = file_path.filename().string();
std::string extension = file_path.extension().string();
std::string name_without_ext;
if (extension.empty()) {
name_without_ext = filename;
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: No extension found, filename='%1%'") % filename;
} else {
name_without_ext = filename.substr(0, filename.size() - extension.size());
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: filename='%1%', extension='%2%', name_without_ext='%3%'")
% filename % extension % name_without_ext;
}
// Generate unique filename with Windows-style numbering: filename(1).ext, filename(2).ext, etc.
size_t version = 1;
boost::filesystem::path unique_path;
do {
std::string new_filename;
if (extension.empty()) {
// No extension: filename(1), filename(2), etc.
new_filename = name_without_ext + "(" + std::to_string(version) + ")";
} else {
// Has extension: filename(1).ext, filename(2).ext, etc.
new_filename = name_without_ext + "(" + std::to_string(version) + ")" + extension;
}
unique_path = parent_dir / new_filename;
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Trying version %1%: '%2%'") % version % unique_path.string();
version++;
} while (boost::filesystem::exists(unique_path) && version < 10000); // Safety limit
if (version >= 10000) {
// If we hit the limit, log a warning and return a timestamp-based name
BOOST_LOG_TRIVIAL(warning) << boost::format("DownloadManager::get_unique_file_path: Too many duplicate files for '%1%', using timestamp-based name")
% original_path;
std::string timestamp = std::to_string(std::time(nullptr));
std::string new_filename;
if (extension.empty()) {
new_filename = name_without_ext + "_" + timestamp;
} else {
new_filename = name_without_ext + "_" + timestamp + extension;
}
unique_path = parent_dir / new_filename;
}
std::string result = unique_path.string();
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Final unique path: '%1%'") % result;
return result;
}
// ============================================================================
// WCP Download Interface (for Web-to-PC communication)
// ============================================================================
size_t DownloadManager::start_wcp_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance,
bool use_original_event_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
auto downloadPath = wxGetApp().app_config->get("download_path");
boost::filesystem::path dest_folder(downloadPath);
boost::filesystem::create_directories(dest_folder);
boost::filesystem::path dest_file = dest_folder / file_name;
// Generate unique file path if file already exists
std::string dest_path = get_unique_file_path(dest_file);
// Update file_name if it was changed due to duplicate
std::string actual_file_name = boost::filesystem::path(dest_path).filename().string();
auto task = std::make_shared<DownloadTask>(task_id,
file_url,
actual_file_name,
dest_path,
wcp_instance,
use_original_event_id);
task->state = DownloadTaskState::Downloading;
m_tasks[task_id] = task;
start_download_impl(task);
return task_id;
}
// ============================================================================
// Internal Download Interface (for PC internal use)
// ============================================================================
size_t DownloadManager::start_internal_download(const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
DownloadCallbacks callbacks) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
boost::filesystem::path dest_path_obj(dest_path);
boost::filesystem::path dest_file_path;
if (boost::filesystem::is_directory(dest_path_obj) || dest_path_obj.filename().empty()) {
// dest_path is a directory, need to append file_name
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::start_internal_download: dest_path '%1%' is a directory, appending file_name '%2%'")
% dest_path % file_name;
dest_file_path = dest_path_obj / file_name;
} else {
// dest_path is already a complete file path (directory + filename)
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::start_internal_download: dest_path '%1%' is a complete file path")
% dest_path;
dest_file_path = dest_path_obj;
}
boost::filesystem::create_directories(dest_file_path.parent_path());
std::string unique_dest_path = get_unique_file_path(dest_file_path);
auto task = std::make_shared<DownloadTask>(task_id,
file_url,
file_name,
unique_dest_path,
std::move(callbacks));
task->state = DownloadTaskState::Downloading;
m_tasks[task_id] = task;
start_download_impl(task);
return task_id;
}
size_t DownloadManager::start_internal_download(const std::string& file_url,
const std::string& file_name,
DownloadCallbacks callbacks) {
// Get default download path
auto downloadPath = wxGetApp().app_config->get("download_path");
boost::filesystem::path dest_folder(downloadPath);
boost::filesystem::create_directories(dest_folder);
boost::filesystem::path dest_file = dest_folder / file_name;
// Generate unique file path if file already exists
std::string dest_path = get_unique_file_path(dest_file);
return start_internal_download(file_url, file_name, dest_path, std::move(callbacks));
}
void DownloadManager::start_download_impl(std::shared_ptr<DownloadTask> task) {
wxGetApp().CallAfter([this, task]() {
try {
// Step 1: Create Http object
Http http = Http::get(task->file_url);
http.timeout_max(0);
// Step 2: Set progress callback
http.on_progress([this, task](Http::Progress progress, bool& cancel) {
// Check if task is canceled or already cleaned up
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
if (m_tasks.find(task->task_id) == m_tasks.end()) {
// Task has been cleaned up, cancel the download
cancel = true;
return;
}
}
if (task->state == DownloadTaskState::Canceled) {
cancel = true;
return;
}
int percent = 0;
if (progress.dltotal > 0) {
percent = (int)(progress.dlnow * 100 / progress.dltotal);
}
task->percent = percent;
// Throttle progress updates: update every 5% or every second
std::lock_guard<std::mutex> lock(m_tasks_mutex);
// Double-check task still exists after acquiring lock
if (m_tasks.find(task->task_id) == m_tasks.end()) {
cancel = true;
return;
}
auto& last_pct = m_last_percent[task->task_id];
auto& last_upd = m_last_update[task->task_id];
auto now = std::chrono::steady_clock::now();
bool should_update = false;
if (percent - last_pct >= 5) {
should_update = true;
last_pct = percent;
} else if (now - last_upd >= std::chrono::seconds(1)) {
should_update = true;
}
if (should_update) {
last_upd = now;
wxGetApp().CallAfter([this, task, percent, progress]() {
// Check if task still exists before sending update
std::lock_guard<std::mutex> lock(m_tasks_mutex);
if (m_tasks.find(task->task_id) != m_tasks.end() &&
task->state != DownloadTaskState::Canceled) {
send_progress_update(task, percent, progress.dlnow, progress.dltotal);
}
});
}
});
// Step 3: Set complete callback
http.on_complete([this, task](std::string body, unsigned status) {
wxGetApp().CallAfter([this, task, body]() {
// Check if task still exists and is not canceled (without lock to avoid deadlock with cleanup_task)
if (task->state == DownloadTaskState::Canceled) {
// Task has been canceled, ignore completion
BOOST_LOG_TRIVIAL(debug) << "DownloadManager: Ignoring complete callback for canceled task " << task->task_id;
return;
}
try {
// Save file
boost::nowide::ofstream file(task->dest_path, std::ios::binary);
if (!file.is_open()) {
std::string error_msg = "Failed to open file for writing: " + task->dest_path;
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg;
// Flush logs immediately for critical errors
Slic3r::flush_logs();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
return;
}
file.write(body.c_str(), body.size());
if (file.fail()) {
std::string error_msg = "Failed to write file: " + task->dest_path;
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", body size: " << body.size();
// Flush logs immediately for critical errors
Slic3r::flush_logs();
file.close();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
return;
}
file.close();
task->state = DownloadTaskState::Completed;
task->percent = 100;
send_complete_update(task, task->dest_path);
cleanup_task(task->task_id);
} catch (std::exception& e) {
std::string error_msg = std::string("File write exception: ") + e.what();
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", file: " << task->dest_path;
// Flush logs immediately for critical errors
Slic3r::flush_logs();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
}
});
});
// Step 4: Set error callback
http.on_error([this, task](std::string body, std::string error, unsigned status) {
wxGetApp().CallAfter([this, task, error, status]() {
// Check if task was canceled (without lock to avoid deadlock with cleanup_task)
if (task->state == DownloadTaskState::Canceled) {
// Task was canceled, ignore error callback (cancel already handled cleanup)
BOOST_LOG_TRIVIAL(debug) << "DownloadManager: Ignoring error callback for canceled task " << task->task_id;
return;
}
std::string error_msg = boost::str(boost::format("HTTP error: %1% (status: %2%)") % error % status);
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg
<< ", URL: " << task->file_url
<< ", file: " << task->file_name
<< ", dest: " << task->dest_path;
// Flush logs immediately for critical errors to ensure they are written
Slic3r::flush_logs();
task->state = DownloadTaskState::Error;
task->error_message = error;
send_error_update(task, error);
cleanup_task(task->task_id);
});
});
// Step 5: Start download and save Http::Ptr for cancellation
task->http_object = http.perform();
} catch (std::exception& e) {
std::string error_msg = std::string("Download exception: ") + e.what();
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg
<< ", URL: " << task->file_url
<< ", file: " << task->file_name
<< ", dest: " << task->dest_path;
task->state = DownloadTaskState::Error;
task->error_message = e.what();
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
}
//this function not currently in use
bool DownloadManager::cancel_download(size_t task_id) {
std::shared_ptr<SSWCP_Instance> wcp_to_destroy;
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it == m_tasks.end()) {
return false;
}
auto task = it->second;
if (task->state == DownloadTaskState::Downloading) {
task->state = DownloadTaskState::Canceled;
if (task->http_object) {
task->http_object->cancel();
}
// Only for WCP downloads
if (task->is_wcp_download()) {
wcp_to_destroy = task->wcp_instance.lock();
} else {
task->callbacks.on_error = nullptr;
task->callbacks.on_progress = nullptr;
task->callbacks.on_complete = nullptr;
}
// Cleanup task directly (already holding the lock, don't call cleanup_task)
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
} else {
return false;
}
}
if (wcp_to_destroy) {
wcp_to_destroy->finish_job();
}
return true;
}
// this function not currently in use
bool DownloadManager::pause_download(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == DownloadTaskState::Downloading) {
it->second->state = DownloadTaskState::Paused;
return true;
}
return false;
}
// this function not currently in use
bool DownloadManager::resume_download(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == DownloadTaskState::Paused) {
return false;
}
return false;
}
// this function not currently in use
DownloadTaskState DownloadManager::get_task_state(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second->state;
}
return DownloadTaskState::Error;
}
// this function not currently in use
std::shared_ptr<DownloadTask> DownloadManager::get_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second;
}
return nullptr;
}
// this function not currently in use
std::vector<std::shared_ptr<DownloadTask>> DownloadManager::get_all_tasks() {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
std::vector<std::shared_ptr<DownloadTask>> result;
result.reserve(m_tasks.size());
for (const auto& pair : m_tasks) {
result.push_back(pair.second);
}
return result;
}
// ============================================================================
// Progress/Complete/Error Update Handlers
// ============================================================================
void DownloadManager::send_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (task->is_wcp_download()) {
send_wcp_progress_update(task, percent, downloaded, total);
} else {
call_internal_progress_callback(task, percent, downloaded, total);
}
}
void DownloadManager::send_wcp_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (!task->use_original_event_id) {
return;
}
if (auto wcp = task->wcp_instance.lock()) {
json progress_data;
progress_data["task_id"] = task->task_id;
progress_data["percent"] = percent;
progress_data["downloaded"] = downloaded;
progress_data["total"] = total;
progress_data["state"] = "downloading";
wcp->m_res_data = progress_data;
wcp->m_status = 0;
wcp->m_msg = "Download progress";
json header;
if (task->use_original_event_id) {
header["event_id"] = wcp->m_event_id;
} else {
header["event_id"] = wcp->m_event_id + "_progress";
}
header["command"] = "download_progress";
wcp->m_header = header;
wcp->send_to_js();
}
}
void DownloadManager::call_internal_progress_callback(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (task->callbacks.on_progress && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_progress(task->task_id, percent, downloaded, total);
}
}
void DownloadManager::send_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (task->is_wcp_download()) {
send_wcp_complete_update(task, file_path);
} else {
call_internal_complete_callback(task, file_path);
}
}
void DownloadManager::send_wcp_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (!task->use_original_event_id) {
return;
}
if (auto wcp = task->wcp_instance.lock()) {
json complete_data;
complete_data["task_id"] = task->task_id;
complete_data["file_path"] = file_path;
complete_data["file_name"] = task->file_name;
complete_data["percent"] = 100;
complete_data["state"] = "completed";
wcp->m_res_data = complete_data;
wcp->m_status = 0;
wcp->m_msg = "Download completed";
wcp->send_to_js();
wcp->finish_job();
}
}
void DownloadManager::call_internal_complete_callback(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (task->callbacks.on_complete && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_complete(task->task_id, file_path);
}
}
void DownloadManager::send_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (task->is_wcp_download()) {
send_wcp_error_update(task, error);
} else {
call_internal_error_callback(task, error);
}
}
void DownloadManager::send_wcp_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (!task->use_original_event_id) {
return;
}
if (auto wcp = task->wcp_instance.lock()) {
json error_data;
error_data["task_id"] = task->task_id;
error_data["error"] = error;
error_data["state"] = "error";
wcp->m_res_data = error_data;
wcp->m_status = -1;
wcp->m_msg = error;
wcp->send_to_js();
wcp->finish_job();
}
}
void DownloadManager::call_internal_error_callback(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (task->callbacks.on_error && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_error(task->task_id, error);
}
}
void DownloadManager::cleanup_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
}
}} // namespace Slic3r::GUI
+210
View File
@@ -0,0 +1,210 @@
#ifndef slic3r_DownloadManager_hpp_
#define slic3r_DownloadManager_hpp_
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <chrono>
#include <functional>
#include "../Utils/Http.hpp"
#include "SSWCP.hpp"
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp"
namespace Slic3r { namespace GUI {
// Download task state (renamed to avoid conflict with Downloader::DownloadState)
enum class DownloadTaskState {
Pending,
Downloading,
Paused,
Completed,
Error,
Canceled
};
// Download callback interface for internal downloads
struct DownloadCallbacks {
std::function<void(size_t task_id, int percent, size_t downloaded, size_t total)> on_progress;
std::function<void(size_t task_id, const std::string& file_path)> on_complete;
std::function<void(size_t task_id, const std::string& error)> on_error;
DownloadCallbacks() = default;
DownloadCallbacks(
std::function<void(size_t, int, size_t, size_t)> progress,
std::function<void(size_t, const std::string&)> complete,
std::function<void(size_t, const std::string&)> error)
: on_progress(std::move(progress))
, on_complete(std::move(complete))
, on_error(std::move(error))
{}
};
// Download task information
struct DownloadTask {
size_t task_id;
std::string file_url;
std::string file_name;
std::string dest_path;
std::weak_ptr<SSWCP_Instance> wcp_instance;
DownloadCallbacks callbacks;
Http::Ptr http_object;
DownloadTaskState state;
int percent;
std::string error_message;
bool auto_finish_job;
bool use_original_event_id;
// Constructor for WCP downloads
DownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, std::shared_ptr<SSWCP_Instance> instance,
bool use_original_event = false)
: task_id(id), file_url(url), file_name(name), dest_path(path)
, wcp_instance(instance), state(DownloadTaskState::Pending), percent(0)
, auto_finish_job(false), use_original_event_id(use_original_event)
{}
// Constructor for internal downloads
DownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, DownloadCallbacks cb)
: task_id(id), file_url(url), file_name(name), dest_path(path)
, callbacks(std::move(cb)), state(DownloadTaskState::Pending), percent(0)
, auto_finish_job(false), use_original_event_id(false)
{}
// Check if this is a WCP download
bool is_wcp_download() const {
return !wcp_instance.expired();
}
};
class DownloadManager {
public:
static DownloadManager& getInstance() {
static DownloadManager instance;
return instance;
}
// ============================================================================
// WCP Download Interface (for Web-to-PC communication)
// ============================================================================
size_t start_wcp_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance,
bool use_original_event_id = false);
// ============================================================================
// Internal Download Interface (for PC internal use)
// ============================================================================
size_t start_internal_download(const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
DownloadCallbacks callbacks);
size_t start_internal_download(const std::string& file_url,
const std::string& file_name,
DownloadCallbacks callbacks);
// ============================================================================
// Common Interface (works for both WCP and internal downloads)
// ============================================================================
// Cancel a download task
bool cancel_download(size_t task_id);
// Pause a download task (if needed)
bool pause_download(size_t task_id);
// Resume a download task (if needed)
bool resume_download(size_t task_id);
// Get task state
DownloadTaskState get_task_state(size_t task_id);
// Get task information
std::shared_ptr<DownloadTask> get_task(size_t task_id);
// Get all active tasks
std::vector<std::shared_ptr<DownloadTask>> get_all_tasks();
private:
DownloadManager() = default;
~DownloadManager() = default;
DownloadManager(const DownloadManager&) = delete;
DownloadManager& operator=(const DownloadManager&) = delete;
std::mutex m_tasks_mutex;
std::unordered_map<size_t, std::shared_ptr<DownloadTask>> m_tasks;
std::atomic<size_t> m_next_task_id{1};
// Track last progress update for throttling
std::unordered_map<size_t, int> m_last_percent;
std::unordered_map<size_t, std::chrono::steady_clock::time_point> m_last_update;
// ============================================================================
// Internal Implementation
// ============================================================================
// Common download implementation (used by both WCP and internal downloads)
void start_download_impl(std::shared_ptr<DownloadTask> task);
// Send progress update (handles both WCP and internal modes)
void send_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// Send completion message (handles both WCP and internal modes)
void send_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// Send error message (handles both WCP and internal modes)
void send_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error);
// WCP-specific: Send progress update via WCP instance
void send_wcp_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// WCP-specific: Send completion via WCP instance
void send_wcp_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// WCP-specific: Send error via WCP instance
void send_wcp_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error);
// Internal-specific: Call progress callback
void call_internal_progress_callback(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// Internal-specific: Call complete callback
void call_internal_complete_callback(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// Internal-specific: Call error callback
void call_internal_error_callback(std::shared_ptr<DownloadTask> task,
const std::string& error);
// Clean up completed task
void cleanup_task(size_t task_id);
// Generate unique file path if file already exists
// Returns path like "file(1).zip", "file(2).zip" etc.
static std::string get_unique_file_path(const boost::filesystem::path& file_path);
};
}} // namespace Slic3r::GUI
#endif // slic3r_DownloadManager_hpp_
+41 -34
View File
@@ -13,7 +13,7 @@
#include "slic3r/GUI/WebPresetDialog.hpp" #include "slic3r/GUI/WebPresetDialog.hpp"
#include "slic3r/GUI/SSWCP.hpp" #include "slic3r/GUI/SSWCP.hpp"
#include "slic3r/GUI/WCPDownloadManager.hpp" #include "slic3r/GUI/DownloadManager.hpp"
#include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/Config/Version.hpp" #include "slic3r/Config/Version.hpp"
@@ -1133,7 +1133,7 @@ GUI_App::GUI_App()
, m_imgui(new ImGuiWrapper()) , m_imgui(new ImGuiWrapper())
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>()) , m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_downloader(std::make_unique<Downloader>()) , m_downloader(std::make_unique<Downloader>())
, m_wcp_download_manager(&WCPDownloadManager::getInstance()) , m_download_manager(&DownloadManager::getInstance())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>()) , m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{ {
StartupProfiler profiler("GUI_App::GUI_App"); StartupProfiler profiler("GUI_App::GUI_App");
@@ -1156,7 +1156,9 @@ GUI_App::GUI_App()
m_page_http_server.set_request_handler(HttpServer::web_server_handle_request); m_page_http_server.set_request_handler(HttpServer::web_server_handle_request);
m_page_http_server.start(); m_page_http_server.start();
profiler.mark("m_page_http_server.start"); profiler.mark("m_page_http_server.start");
BOOST_LOG_TRIVIAL(info) << "[Flutter] Version:" << common::get_flutter_version();
BOOST_LOG_TRIVIAL(info) << "[Profile] Version:" << common::get_profile_version();
flush_logs();
m_fltviews.set_app(this); m_fltviews.set_app(this);
profiler.mark("m_fltviews.set_app"); profiler.mark("m_fltviews.set_app");
} }
@@ -2089,8 +2091,8 @@ void GUI_App::init_app_config()
std::time_t t = std::time(0); std::time_t t = std::time(0);
std::tm * now_time = std::localtime(&t); std::tm * now_time = std::localtime(&t);
std::stringstream buf; std::stringstream buf;
buf << std::put_time(now_time, "debug_%a_%b_%d_%H_%M_%S_"); buf << std::put_time(now_time, "%Y-%m-%d-%H-%M-%S");
buf << get_current_pid() << ".log"; buf << ".log";
std::string log_filename = buf.str(); std::string log_filename = buf.str();
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
set_log_path_and_level(log_filename, 5); set_log_path_and_level(log_filename, 5);
@@ -2613,6 +2615,8 @@ bool GUI_App::on_init_inner()
} }
if (!skip_this_version || evt.GetInt() != 0) { if (!skip_this_version || evt.GetInt() != 0) {
wxString extmsg = wxString::FromUTF8(version_info.description); wxString extmsg = wxString::FromUTF8(version_info.description);
if(!m_updateDialog)
return;
m_updateDialog->update_version_info(extmsg, version_info.version_str); m_updateDialog->update_version_info(extmsg, version_info.version_str);
if (evt.GetInt() != 0) { if (evt.GetInt() != 0) {
m_updateDialog->m_button_skip_version->Hide(); m_updateDialog->m_button_skip_version->Hide();
@@ -2760,12 +2764,15 @@ bool GUI_App::on_init_inner()
BOOST_LOG_TRIVIAL(info) << "create the main window"; BOOST_LOG_TRIVIAL(info) << "create the main window";
mainframe = new MainFrame(); mainframe = new MainFrame();
m_updateDialog = new UpdateVersionDialog(mainframe); if (!m_updateDialog)
m_updateDialog->Hide(); {
m_updateDialog->Bind(EVT_DOWN_URL_PACK, [this](wxCommandEvent& event) { m_updateDialog = new UpdateVersionDialog(mainframe);
auto downloadUlr = m_updateDialog->getUrl(); m_updateDialog->Hide();
wxLaunchDefaultBrowser(downloadUlr); m_updateDialog->Bind(EVT_DOWN_URL_PACK, [this](wxCommandEvent& event) {
}); auto downloadUlr = m_updateDialog->getUrl();
wxLaunchDefaultBrowser(downloadUlr);
});
}
profiler.mark("mainframe construction"); profiler.mark("mainframe construction");
// hide settings tabs after first Layout // hide settings tabs after first Layout
@@ -3635,6 +3642,15 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
switch_window_pools(); switch_window_pools();
mainframe = new MainFrame(); mainframe = new MainFrame();
if (!m_updateDialog) {
m_updateDialog = new UpdateVersionDialog(mainframe);
m_updateDialog->Hide();
m_updateDialog->Bind(EVT_DOWN_URL_PACK, [this](wxCommandEvent& event) {
auto downloadUlr = m_updateDialog->getUrl();
wxLaunchDefaultBrowser(downloadUlr);
});
}
if (is_editor()) if (is_editor())
// hide settings tabs after first Layout // hide settings tabs after first Layout
mainframe->select_tab(size_t(MainFrame::tp3DEditor)); mainframe->select_tab(size_t(MainFrame::tp3DEditor));
@@ -5002,7 +5018,7 @@ void GUI_App::check_new_version_sf(bool show_tips, bool by_user)
BOOST_LOG_TRIVIAL(fatal) << "request server soft update data error:" << errorMsg; BOOST_LOG_TRIVIAL(fatal) << "request server soft update data error:" << errorMsg;
} }
}) })
.perform_sync(); .perform();
} }
void GUI_App::process_network_msg(std::string dev_id, std::string msg) void GUI_App::process_network_msg(std::string dev_id, std::string msg)
{ {
@@ -5422,20 +5438,20 @@ void GUI_App::stop_sync_user_preset()
} }
} }
void GUI_App::start_http_server() //void GUI_App::start_http_server()
{ //{
if (!m_http_server.is_started()) // if (!m_http_server.is_started())
m_http_server.start(); // m_http_server.start();
} //}
void GUI_App::stop_http_server() //void GUI_App::stop_http_server()
{ //{
m_http_server.stop(); // m_http_server.stop();
} //}
void GUI_App::start_page_http_server() void GUI_App::start_page_http_server()
{ {
if (!m_page_http_server.is_started()) if (!m_page_http_server.is_started())
m_page_http_server; m_page_http_server.start();
} }
void GUI_App::stop_page_http_server() void GUI_App::stop_page_http_server()
{ {
@@ -6589,9 +6605,9 @@ Downloader* GUI_App::downloader()
return m_downloader.get(); return m_downloader.get();
} }
WCPDownloadManager* GUI_App::wcp_download_manager() DownloadManager* GUI_App::download_manager()
{ {
return m_wcp_download_manager; return m_download_manager;
} }
void GUI_App::load_url(wxString url) void GUI_App::load_url(wxString url)
@@ -7086,16 +7102,7 @@ bool GUI_App::config_wizard_startup()
BOOST_LOG_TRIVIAL(info) << "finished run wizard"; BOOST_LOG_TRIVIAL(info) << "finished run wizard";
return true; return true;
} /*else if (get_app_config()->legacy_datadir()) { }
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
run_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}*/
if (isAgree.empty()) if (isAgree.empty())
{ {
+6 -6
View File
@@ -89,7 +89,7 @@ class Plater;
class ParamsPanel; class ParamsPanel;
class NotificationManager; class NotificationManager;
class Downloader; class Downloader;
class WCPDownloadManager; class DownloadManager;
struct GUI_InitParams; struct GUI_InitParams;
class ParamsDialog; class ParamsDialog;
class HMSQuery; class HMSQuery;
@@ -298,7 +298,7 @@ private:
size_t m_instance_hash_int; size_t m_instance_hash_int;
std::unique_ptr<Downloader> m_downloader; std::unique_ptr<Downloader> m_downloader;
WCPDownloadManager* m_wcp_download_manager; DownloadManager* m_download_manager;
//BBS //BBS
bool m_is_closing {false}; bool m_is_closing {false};
@@ -336,7 +336,7 @@ private:
bool m_side_popup_status{false}; bool m_side_popup_status{false};
bool m_show_http_errpr_msgdlg{false}; bool m_show_http_errpr_msgdlg{false};
wxString m_info_dialog_content; wxString m_info_dialog_content;
HttpServer m_http_server; //HttpServer m_http_server;
public: public:
HttpServer m_page_http_server; HttpServer m_page_http_server;
@@ -604,8 +604,8 @@ private:
void sync_preset(Preset* preset); void sync_preset(Preset* preset);
void start_sync_user_preset(bool with_progress_dlg = false); void start_sync_user_preset(bool with_progress_dlg = false);
void stop_sync_user_preset(); void stop_sync_user_preset();
void start_http_server(); //void start_http_server();
void stop_http_server(); //void stop_http_server();
// page loading http server // page loading http server
void start_page_http_server(); void start_page_http_server();
@@ -686,7 +686,7 @@ private:
Model& model(); Model& model();
NotificationManager * notification_manager(); NotificationManager * notification_manager();
Downloader* downloader(); Downloader* downloader();
WCPDownloadManager* wcp_download_manager(); DownloadManager* download_manager();
std::string m_mall_model_download_url; std::string m_mall_model_download_url;
+20
View File
@@ -330,11 +330,25 @@ bool CheckboxFileDialog::get_checkbox_value() const
} }
// GTK requires width >= -1 and height > 0 for gtk_window_resize/set_size_request.
// Use minimum 100 to avoid 0 or negative dimensions from bad config or Intersect().
static const int WINDOW_MIN_WIDTH = 100;
static const int WINDOW_MIN_HEIGHT = 100;
static void clamp_rect_to_minimum_size(wxRect &rect)
{
if (rect.width < WINDOW_MIN_WIDTH)
rect.width = WINDOW_MIN_WIDTH;
if (rect.height < WINDOW_MIN_HEIGHT)
rect.height = WINDOW_MIN_HEIGHT;
}
WindowMetrics WindowMetrics::from_window(wxTopLevelWindow *window) WindowMetrics WindowMetrics::from_window(wxTopLevelWindow *window)
{ {
WindowMetrics res; WindowMetrics res;
res.rect = window->GetScreenRect(); res.rect = window->GetScreenRect();
res.maximized = window->IsMaximized(); res.maximized = window->IsMaximized();
clamp_rect_to_minimum_size(res.rect);
return res; return res;
} }
@@ -362,6 +376,7 @@ boost::optional<WindowMetrics> WindowMetrics::deserialize(const std::string &str
WindowMetrics res; WindowMetrics res;
res.rect = wxRect(metrics[0], metrics[1], metrics[2], metrics[3]); res.rect = wxRect(metrics[0], metrics[1], metrics[2], metrics[3]);
clamp_rect_to_minimum_size(res.rect);
res.maximized = metrics[4] != 0; res.maximized = metrics[4] != 0;
return res; return res;
@@ -370,6 +385,11 @@ boost::optional<WindowMetrics> WindowMetrics::deserialize(const std::string &str
void WindowMetrics::sanitize_for_display(const wxRect &screen_rect) void WindowMetrics::sanitize_for_display(const wxRect &screen_rect)
{ {
rect = rect.Intersect(screen_rect); rect = rect.Intersect(screen_rect);
// Intersect can yield 0 or negative width/height; GTK asserts on that.
clamp_rect_to_minimum_size(rect);
// Keep within display (e.g. after monitor change)
rect.x = std::max(screen_rect.x, std::min(rect.x, screen_rect.x + screen_rect.width - rect.width));
rect.y = std::max(screen_rect.y, std::min(rect.y, screen_rect.y + screen_rect.height - rect.height));
// Prevent the window from going too far towards the right and/or bottom edge // Prevent the window from going too far towards the right and/or bottom edge
// It's hardcoded here that the threshold is 80% of the screen size // It's hardcoded here that the threshold is 80% of the screen size
+434
View File
@@ -0,0 +1,434 @@
#include "GenericDownloadDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/hyperlink.h>
#include <wx/textctrl.h>
#include <wx/scrolwin.h>
#include <wx/event.h>
#include <wx/dcgraph.h>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "GUI_App.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
namespace Slic3r {
namespace GUI {
GenericDownloadDialog::GenericDownloadDialog(wxString title,
const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
wxWindow* parent)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
, m_title(title)
, m_file_url(file_url)
, m_file_name(file_name)
, m_dest_path(dest_path)
{
std::string icon_path = (boost::format("%1%/images/Snapmaker_OrcaTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
setup_ui();
Bind(wxEVT_CLOSE_WINDOW, &GenericDownloadDialog::on_close, this);
wxGetApp().UpdateDlgDarkUI(this);
}
GenericDownloadDialog::~GenericDownloadDialog()
{
// Set destroying flag first to prevent any callbacks from accessing this object
m_is_destroying = true;
// Cancel any active download before destruction
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
}
void GenericDownloadDialog::setup_ui()
{
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0);
m_simplebook_status = new wxSimplebook(this);
m_simplebook_status->SetSize(wxSize(FromDIP(420), FromDIP(100)));
m_simplebook_status->SetMinSize(wxSize(FromDIP(420), FromDIP(100)));
m_simplebook_status->SetMaxSize(wxSize(FromDIP(420), FromDIP(250)));
// Progress page
m_status_bar = std::make_shared<BBLStatusBarSend>(m_simplebook_status);
m_panel_download = m_status_bar->get_panel();
m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70)));
// Complete page
m_panel_complete = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_complete = new wxBoxSizer(wxVERTICAL);
m_complete_text = new wxStaticText(m_panel_complete, wxID_ANY, _L("Download completed successfully!"),
wxDefaultPosition, wxDefaultSize, 0);
m_complete_text->SetForegroundColour(*wxBLACK);
m_complete_text->Wrap(FromDIP(360));
sizer_complete->Add(m_complete_text, 0, wxALIGN_CENTER | wxALL, 5);
StateColor btn_close_bg(std::pair<wxColour, int>(wxColour(0x90, 0x90, 0x90), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(231, 231, 231), StateColor::Normal));
StateColor btn_close_bd(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_close_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(36, 36, 36), StateColor::Normal));
m_close_button = new Button(m_panel_complete, _L("Close"));
m_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetBackgroundColour(*wxWHITE);
m_close_button->SetBackgroundColor(btn_close_bg);
m_close_button->SetBorderColor(btn_close_bd);
m_close_button->SetTextColor(btn_close_txt);
m_close_button->SetCornerRadius(FromDIP(12));
m_close_button->SetCursor(wxCURSOR_HAND);
m_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this);
sizer_complete->Add(m_close_button, 0, wxALIGN_CENTER | wxALL, 5);
m_panel_complete->SetSizer(sizer_complete);
m_panel_complete->Layout();
sizer_complete->Fit(m_panel_complete);
// Error page
m_panel_error = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_error = new wxBoxSizer(wxVERTICAL);
// Simple label for error message display
m_error_text = new wxStaticText(m_panel_error, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxSize(FromDIP(380), -1),
wxALIGN_LEFT | wxST_ELLIPSIZE_END);
m_error_text->SetForegroundColour(*wxBLACK);
m_error_text->Wrap(FromDIP(380));
sizer_error->Add(m_error_text, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, FromDIP(10));
// Button sizer aligned to right
wxBoxSizer* sizer_buttons = new wxBoxSizer(wxHORIZONTAL);
sizer_buttons->AddStretchSpacer();
StateColor btn_retry_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Hovered), // Same as Normal
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Normal));
// Set border color to same as background to avoid corner color issues
StateColor btn_retry_bd(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Enabled)); // Same as background
StateColor btn_retry_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
m_retry_button = new Button(m_panel_error, _L("Retry"));
m_retry_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_retry_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_retry_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
// Set window background color to white to ensure rounded corners are white
m_retry_button->SetBackgroundColour(*wxWHITE);
m_retry_button->SetBackgroundColor(btn_retry_bg);
m_retry_button->SetBorderColor(btn_retry_bg);
m_retry_button->SetTextColor(btn_retry_txt);
m_retry_button->SetCornerRadius(FromDIP(12));
m_retry_button->SetCursor(wxCURSOR_HAND);
m_retry_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_retry_clicked, this);
// Setup StateColor for determine button (gray style)
StateColor btn_determine_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(231, 231, 231), StateColor::Normal));
StateColor btn_determine_bd(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_determine_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(36, 36, 36), StateColor::Normal));
m_error_close_button = new Button(m_panel_error, _L("Determine"));
m_error_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_error_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_error_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
// Set window background color to white to ensure rounded corners are white
m_error_close_button->SetBackgroundColour(*wxWHITE);
m_error_close_button->SetBackgroundColor(btn_determine_bg);
m_error_close_button->SetBorderColor(btn_determine_bg);
m_error_close_button->SetTextColor(btn_determine_txt);
m_error_close_button->SetCornerRadius(FromDIP(12));
m_error_close_button->SetCursor(wxCURSOR_HAND);
m_error_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this);
sizer_buttons->Add(m_error_close_button, 0, 0);
sizer_buttons->AddSpacer(FromDIP(6));
sizer_buttons->Add(m_retry_button, 0, 0);
sizer_error->AddSpacer(FromDIP(40));
sizer_error->Add(sizer_buttons, 0, wxALIGN_RIGHT | wxRIGHT, FromDIP(24));
m_panel_error->SetSizer(sizer_error);
m_panel_error->Layout();
sizer_error->Fit(m_panel_error);
m_sizer_main->Add(m_simplebook_status, 0, wxALL, FromDIP(16));
m_simplebook_status->AddPage(m_panel_download, wxEmptyString, true);
m_simplebook_status->AddPage(m_panel_complete, wxEmptyString, false);
m_simplebook_status->AddPage(m_panel_error, wxEmptyString, false);
SetSizer(m_sizer_main);
Layout();
Fit();
CentreOnParent();
}
wxString GenericDownloadDialog::format_text(wxStaticText* st, wxString str, int warp)
{
if (wxGetApp().app_config->get("language") != "zh_CN") {
return str;
}
wxString out_txt = str;
wxString count_txt = "";
for (int i = 0; i < str.length(); i++) {
auto text_size = st->GetTextExtent(count_txt);
if (text_size.x < warp) {
count_txt += str[i];
} else {
out_txt.insert(i - 1, '\n');
count_txt = "";
}
}
return out_txt;
}
void GenericDownloadDialog::start_download()
{
show_progress_page();
m_status_bar->set_progress(0);
m_status_bar->set_status_text(_L("Preparing download..."));
m_status_bar->change_button_label(_L("Cancel"));
m_status_bar->set_cancel_callback_fina([this]() {
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
EndModal(wxID_CANCEL);
});
// Create download callbacks
DownloadCallbacks callbacks;
callbacks.on_progress = [this](size_t task_id, int percent, size_t downloaded, size_t total) {
on_download_progress(task_id, percent, downloaded, total);
};
callbacks.on_complete = [this](size_t task_id, const std::string& file_path) {
on_download_complete(task_id, file_path);
};
callbacks.on_error = [this](size_t task_id, const std::string& error) {
on_download_error(task_id, error);
};
// Start download
if (m_dest_path.empty()) {
m_task_id = DownloadManager::getInstance().start_internal_download(
m_file_url, m_file_name, std::move(callbacks));
} else {
m_task_id = DownloadManager::getInstance().start_internal_download(
m_file_url, m_file_name, m_dest_path, std::move(callbacks));
}
}
int GenericDownloadDialog::ShowModal()
{
start_download();
return DPIDialog::ShowModal();
}
void GenericDownloadDialog::on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total)
{
wxGetApp().CallAfter([this, percent, downloaded, total]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
update_progress(percent);
// Format status text
wxString status_text;
if (total > 0) {
double downloaded_mb = downloaded / (1024.0 * 1024.0);
double total_mb = total / (1024.0 * 1024.0);
status_text = wxString::Format(_L("Downloading: %.1f MB / %.1f MB (%d%%)"),
downloaded_mb, total_mb, percent);
} else {
double downloaded_mb = downloaded / (1024.0 * 1024.0);
status_text = wxString::Format(_L("Downloading: %.1f MB..."), downloaded_mb);
}
m_status_bar->set_status_text(status_text);
// Call user callback if set
if (m_on_progress) {
m_on_progress(m_task_id, percent, downloaded, total);
}
});
}
void GenericDownloadDialog::on_download_complete(size_t task_id, const std::string& file_path)
{
wxGetApp().CallAfter([this, file_path]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
m_download_success = true;
m_file_path = file_path;
show_complete_page();
// Mark task as completed - no need to cancel it
m_task_id = 0;
// Call user callback if set
if (m_on_complete) {
m_on_complete(m_task_id, file_path);
}
});
}
void GenericDownloadDialog::on_download_error(size_t task_id, const std::string& error)
{
// Log detailed error information for debugging
BOOST_LOG_TRIVIAL(error) << boost::format("GenericDownloadDialog: Download failed for file '%1%' from URL '%2%'. Error: %3%")
% m_file_name % m_file_url % error;
wxGetApp().CallAfter([this, error]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
m_download_success = false;
m_error_message = error;
show_error_page(error);
// Mark task as completed (failed) - no need to cancel it
m_task_id = 0;
// Call user callback if set
if (m_on_error) {
m_on_error(m_task_id, error);
}
});
}
void GenericDownloadDialog::on_retry_clicked(wxCommandEvent& event)
{
SetTitle(m_title);
if (m_on_retry) {
m_on_retry();
}
start_download();
event.Skip();
}
void GenericDownloadDialog::on_close_clicked(wxCommandEvent& event)
{
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
EndModal(m_download_success ? wxID_OK : wxID_CANCEL);
event.Skip();
}
void GenericDownloadDialog::on_close(wxCloseEvent& event)
{
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
event.Skip();
}
void GenericDownloadDialog::show_progress_page()
{
m_simplebook_status->SetSelection(0);
m_status_bar->set_progress(0);
m_status_bar->show_cancel_button();
}
void GenericDownloadDialog::show_complete_page()
{
//m_simplebook_status->SetSelection(1);
//m_status_bar->hide_cancel_button();
EndModal(wxID_OK);
}
void GenericDownloadDialog::show_error_page(const std::string& error_msg)
{
m_simplebook_status->SetSelection(2);
SetTitle(_L("Donwload failed"));
// Display simple error message: filename + "Download failed"
wxString filename = wxString::FromUTF8(m_file_name.c_str());
wxString error_text = filename + " - Download failed";
// Set error text in simple label
m_error_text->SetLabel(error_text);
m_error_text->Wrap(FromDIP(380));
m_panel_error->Layout();
m_simplebook_status->Layout();
Layout();
Fit();
m_status_bar->hide_cancel_button();
}
void GenericDownloadDialog::update_progress(int percent, const wxString& status_text)
{
m_status_bar->set_progress(percent);
if (!status_text.IsEmpty()) {
m_status_bar->set_status_text(status_text);
}
}
void GenericDownloadDialog::on_dpi_changed(const wxRect &suggested_rect)
{
// Handle DPI changes if needed
}
}} // namespace Slic3r::GUI
+113
View File
@@ -0,0 +1,113 @@
#ifndef slic3r_GenericDownloadDialog_hpp_
#define slic3r_GenericDownloadDialog_hpp_
#include <string>
#include <functional>
#include <memory>
#include <atomic>
#include "GUI_Utils.hpp"
#include <wx/dialog.h>
#include <wx/simplebook.h>
#include "BBLStatusBar.hpp"
#include "BBLStatusBarSend.hpp"
#include "Jobs/Worker.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
#include "Widgets/Button.hpp"
class wxBoxSizer;
class wxPanel;
class wxStaticText;
class wxHyperlinkCtrl;
namespace Slic3r {
namespace GUI {
// Generic download dialog for custom download tasks with progress display
class GenericDownloadDialog : public DPIDialog
{
public:
// Callback types
using DownloadCallback = std::function<void(size_t task_id, int percent, size_t downloaded, size_t total)>;
using CompleteCallback = std::function<void(size_t task_id, const std::string& file_path)>;
using ErrorCallback = std::function<void(size_t task_id, const std::string& error)>;
using RetryCallback = std::function<void()>;
GenericDownloadDialog(wxString title,
const std::string& file_url,
const std::string& file_name,
const std::string& dest_path = "",
wxWindow* parent = nullptr);
~GenericDownloadDialog();
// Start download
void start_download();
// Set callbacks (optional)
void set_on_progress(DownloadCallback callback) { m_on_progress = callback; }
void set_on_complete(CompleteCallback callback) { m_on_complete = callback; }
void set_on_error(ErrorCallback callback) { m_on_error = callback; }
void set_on_retry(RetryCallback callback) { m_on_retry = callback; }
// Get download result
bool is_success() const { return m_download_success; }
std::string get_file_path() const { return m_file_path; }
std::string get_error_message() const { return m_error_message; }
// Show modal and return result
int ShowModal() override;
protected:
void on_close(wxCloseEvent& event);
void on_dpi_changed(const wxRect &suggested_rect) override;
wxString format_text(wxStaticText* st, wxString str, int warp);
// Event handlers
void on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total);
void on_download_complete(size_t task_id, const std::string& file_path);
void on_download_error(size_t task_id, const std::string& error);
void on_retry_clicked(wxCommandEvent& event);
void on_close_clicked(wxCommandEvent& event);
private:
void setup_ui();
void show_progress_page();
void show_complete_page();
void show_error_page(const std::string& error_msg);
void update_progress(int percent, const wxString& status_text = "");
wxString m_title;
std::string m_file_url;
std::string m_file_name;
std::string m_dest_path;
size_t m_task_id{0};
bool m_download_success{false};
std::string m_file_path;
std::string m_error_message;
// Callbacks
DownloadCallback m_on_progress;
CompleteCallback m_on_complete;
ErrorCallback m_on_error;
RetryCallback m_on_retry;
// UI components
wxSimplebook* m_simplebook_status{nullptr};
std::shared_ptr<BBLStatusBarSend> m_status_bar;
wxPanel* m_panel_download{nullptr};
wxPanel* m_panel_complete{nullptr};
wxPanel* m_panel_error{nullptr};
wxStaticText* m_complete_text{nullptr};
wxStaticText* m_error_text{nullptr};
Button* m_retry_button{nullptr};
Button* m_close_button{nullptr};
Button* m_error_close_button{nullptr};
std::atomic<bool> m_is_destroying{false};
};
}} // namespace Slic3r::GUI
#endif // slic3r_GenericDownloadDialog_hpp_
+59 -21
View File
@@ -228,13 +228,23 @@ void HttpServer::IOServer::stop_all()
HttpServer::IOServer::IOServer(HttpServer& server) : server(server), acceptor(io_service) HttpServer::IOServer::IOServer(HttpServer& server) : server(server), acceptor(io_service)
{ {
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), server.port); try {
acceptor.open(endpoint.protocol()); boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), server.port);
acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); acceptor.open(endpoint.protocol());
acceptor.bind(endpoint); acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
}
catch (const boost::system::system_error& errorInfo)
{
BOOST_LOG_TRIVIAL(error) << "local server start failed with port:" << server.port;
BOOST_LOG_TRIVIAL(error) << "local server start failed with errorInfo:" << errorInfo.what();
}
} }
HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port) {} HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port)
{
std::cout << "local server init";
}
HttpServer::~HttpServer() HttpServer::~HttpServer()
{ {
@@ -265,9 +275,12 @@ boost::asio::ip::port_type HttpServer::find_available_port(boost::asio::ip::port
// 尝试从起始端口开始查找可用端口 // 尝试从起始端口开始查找可用端口
for (boost::asio::ip::port_type p = start_port; p < start_port + 1000; ++p) { for (boost::asio::ip::port_type p = start_port; p < start_port + 1000; ++p) {
if (is_port_available(p)) { if (is_port_available(p)) {
BOOST_LOG_TRIVIAL(error) << "use new port for start server:"<<p;
return p; return p;
} }
} }
BOOST_LOG_TRIVIAL(fatal) << "no available port for start server:";
throw std::runtime_error("No available ports found"); throw std::runtime_error("No available ports found");
} }
@@ -302,6 +315,7 @@ void HttpServer::start()
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (!start_http_server) { if (!start_http_server) {
BOOST_LOG_TRIVIAL(error) << "Failed to start HTTP server:" << port;
throw std::runtime_error("Failed to start HTTP server"); throw std::runtime_error("Failed to start HTTP server");
} }
@@ -370,20 +384,20 @@ void HttpServer::restart()
bool HttpServer::is_healthy() bool HttpServer::is_healthy()
{ {
if (!start_http_server || !server_) { if (!start_http_server || !server_) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed: server not started or server object is null"; BOOST_LOG_TRIVIAL(fatal) << "Health check failed: server not started or server object is null";
return false; return false;
} }
try { try {
// 检查acceptor是否正常打开 // 检查acceptor是否正常打开
if (!server_->acceptor.is_open()) { if (!server_->acceptor.is_open()) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed: acceptor is not open"; BOOST_LOG_TRIVIAL(fatal) << "Health check failed: acceptor is not open";
return false; return false;
} }
// 检查io_service是否正在运行 // 检查io_service是否正在运行
if (server_->io_service.stopped()) { if (server_->io_service.stopped()) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed: io_service is stopped"; BOOST_LOG_TRIVIAL(fatal) << "Health check failed: io_service is stopped";
return false; return false;
} }
@@ -401,10 +415,10 @@ bool HttpServer::is_healthy()
return true; return true;
} }
BOOST_LOG_TRIVIAL(debug) << "Health check failed: test connection failed with error: " << ec.message(); BOOST_LOG_TRIVIAL(fatal) << "Health check failed: test connection failed with error: " << ec.message();
return false; return false;
} catch (const std::exception& e) { } catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed with exception: " << e.what(); BOOST_LOG_TRIVIAL(fatal) << "Health check failed with exception: " << e.what();
return false; return false;
} }
} }
@@ -457,7 +471,7 @@ void HttpServer::start_health_check()
// 检查服务器是否健康,或者服务器是否已经停止运行 // 检查服务器是否健康,或者服务器是否已经停止运行
if ((start_http_server && !is_healthy()) || !start_http_server) { if ((start_http_server && !is_healthy()) || !start_http_server) {
BOOST_LOG_TRIVIAL(warning) << "HTTP server health check failed or server stopped, performing restart..."; BOOST_LOG_TRIVIAL(error) << "HTTP server health check failed or server stopped, performing restart...";
try { try {
// 在健康检查线程中直接执行重启,避免通过标志传递 // 在健康检查线程中直接执行重启,避免通过标志传递
restart(); restart();
@@ -482,7 +496,7 @@ void HttpServer::stop_health_check()
std::lock_guard<std::mutex> lock(m_health_check_mutex); std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (!m_health_check_enabled) { if (!m_health_check_enabled) {
BOOST_LOG_TRIVIAL(debug) << "Health check is not running"; BOOST_LOG_TRIVIAL(error) << "Health check is not running";
return; return;
} }
@@ -697,6 +711,11 @@ std::shared_ptr<HttpServer::Response> HttpServer::web_server_handle_request(cons
std::string file_path = map_url_to_file_path(url); std::string file_path = map_url_to_file_path(url);
if (file_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "file path is null for: " << url;
}
BOOST_LOG_TRIVIAL(info) << "Handling file_path request for URL: " << file_path; BOOST_LOG_TRIVIAL(info) << "Handling file_path request for URL: " << file_path;
return std::make_shared<ResponseFile>(file_path); return std::make_shared<ResponseFile>(file_path);
} }
@@ -715,10 +734,17 @@ std::string HttpServer::map_url_to_file_path(const std::string& url)
} }
if (trimmed_url == "/") { if (trimmed_url == "/") {
trimmed_url = "/flutter_web/index.html"; // 默认首页 trimmed_url = "/flutter_web/index.html"; // defualt home page
} else if (trimmed_url.substr(0, 11) == "/localfile/") { }
else if (trimmed_url.substr(0, 11) == "/localfile/") {
auto real_path = trimmed_url.substr(11); auto real_path = trimmed_url.substr(11);
return real_path.ToStdString(wxConvUTF8); auto realUTF8Path = real_path.ToStdString(wxConvUTF8);
if (realUTF8Path.empty()) {
BOOST_LOG_TRIVIAL(error) << "realUTF8Path is null for: " << trimmed_url;
}
return realUTF8Path;
} }
auto data_web_path = boost::filesystem::path(data_dir()) / "web"; auto data_web_path = boost::filesystem::path(data_dir()) / "web";
if (!boost::filesystem::exists(data_web_path / "flutter_web")) { if (!boost::filesystem::exists(data_web_path / "flutter_web")) {
@@ -727,13 +753,25 @@ std::string HttpServer::map_url_to_file_path(const std::string& url)
copy_directory_recursively(source_path, target_path); copy_directory_recursively(source_path, target_path);
} }
if (trimmed_url.find("flutter_web") == std::string::npos) { wxString res = "";
wxString res = wxString::FromUTF8(resources_dir()) + trimmed_url; if (trimmed_url.find("flutter_web") == std::string::npos)
return res.ToStdString(wxConvUTF8); {
} else { res = wxString::FromUTF8(resources_dir()) + trimmed_url;
wxString res = wxString::FromUTF8(data_dir()) + trimmed_url;
return res.ToStdString(wxConvUTF8);
} }
else
{
res = wxString::FromUTF8(data_dir()) + trimmed_url;
}
auto strUTF8 = res.ToStdString(wxConvUTF8);
if (strUTF8.empty())
{
BOOST_LOG_TRIVIAL(error) << "strUTF8 is null for: " << res;
}
return strUTF8;
} }
void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut) void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut)
+64 -111
View File
@@ -75,7 +75,7 @@
#endif // _WIN32 #endif // _WIN32
#include <slic3r/GUI/CreatePresetsDialog.hpp> #include <slic3r/GUI/CreatePresetsDialog.hpp>
#include "sentry_wrapper/SentryWrapper.hpp" #include "sentry_wrapper/SentryWrapper.hpp"
#include "GenericDownloadDialog.hpp"
#define UPDATE_BUSER true #define UPDATE_BUSER true
#define UPDATE_BUAUTO false #define UPDATE_BUAUTO false
@@ -270,6 +270,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
// Load the icon either from the exe, or from the ico file. // Load the icon either from the exe, or from the ico file.
SetIcon(main_frame_icon(wxGetApp().get_app_mode())); SetIcon(main_frame_icon(wxGetApp().get_app_mode()));
#ifdef __WXGTK__
// GTK/X11: set minimum size before any layout so gtk_widget_set_size_request and
// gtk_window_resize never see 0 or negative dimensions (avoids assertion failures).
SetMinSize(wxGetApp().get_min_size());
#endif
// initialize tabpanel and menubar // initialize tabpanel and menubar
init_tabpanel(); init_tabpanel();
if (wxGetApp().is_gcode_viewer()) if (wxGetApp().is_gcode_viewer())
@@ -1149,9 +1155,9 @@ void MainFrame::init_tabpanel() {
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) { Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
wxString url = evt.GetString(); wxString url = evt.GetString();
wxString key = evt.GetAPIkey(); wxString key = evt.GetAPIkey();
//select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key); m_printer_view->load_url(url, key);
}); });
m_printer_view->Hide(); m_printer_view->Hide();
if (wxGetApp().is_enable_multi_machine()) { if (wxGetApp().is_enable_multi_machine()) {
@@ -1248,7 +1254,6 @@ void MainFrame::show_device(bool bBBLPrinter) {
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) { Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString(); wxString url = evt.GetString();
wxString key = evt.GetAPIkey(); wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key); m_printer_view->load_url(url, key);
}); });
} }
@@ -2254,7 +2259,8 @@ static wxMenu* generate_help_menu()
// //TODO // //TODO
// }); // });
// Check New Version // Check New Version
append_menu_item(helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"), append_menu_item(
helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"),
[](wxCommandEvent&) { [](wxCommandEvent&) {
wxGetApp().check_new_version_sf(true, UPDATE_BUSER); wxGetApp().check_new_version_sf(true, UPDATE_BUSER);
}, "", nullptr, []() { }, "", nullptr, []() {
@@ -2813,23 +2819,10 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { m_plater->toggle_show_wireframe(); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this, [this](wxCommandEvent&) { m_plater->toggle_show_wireframe(); m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); }, this,
[this]() { return m_plater->is_wireframe_enabled(); }, [this]() { return m_plater->is_show_wireframe(); }, this);*/ [this]() { return m_plater->is_wireframe_enabled(); }, [this]() { return m_plater->is_show_wireframe(); }, this);*/
//viewMenu->AppendSeparator();
////BBS orthogonal view
//append_menu_check_item(viewMenu, wxID_ANY, _L("Show Edges(TODO)"), _L("Show Edges."),
// [this](wxCommandEvent& evt) {
// wxGetApp().app_config->set("show_build_edges", evt.GetInt() == 1 ? "true" : "false");
// }, nullptr, [this]() {return can_select(); }, [this]() {
// std::string show_build_edges = wxGetApp().app_config->get("show_build_edges");
// return show_build_edges.compare("true") == 0;
// }, this);
} }
wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt));
//TODO remove #ifdef __APPLE__
//auto config_wizard_name = _(ConfigWizard::name(true) + "(Debug)");
//const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str());
//auto config_item = new wxMenuItem(m_topbar->GetTopMenu(), ConfigMenuWizard + config_id_base, config_wizard_name, config_wizard_tooltip);
#ifdef __APPLE__
wxWindowID bambu_studio_id_base = wxWindow::NewControlId(int(2)); wxWindowID bambu_studio_id_base = wxWindow::NewControlId(int(2));
wxMenu* parent_menu = m_menubar->OSXGetAppleMenu(); wxMenu* parent_menu = m_menubar->OSXGetAppleMenu();
//auto preference_item = new wxMenuItem(parent_menu, OrcaSlicerMenuPreferences + bambu_studio_id_base, _L("Preferences") + "\t" + ctrl + ",", ""); //auto preference_item = new wxMenuItem(parent_menu, OrcaSlicerMenuPreferences + bambu_studio_id_base, _L("Preferences") + "\t" + ctrl + ",", "");
@@ -2838,98 +2831,11 @@ void MainFrame::init_menubar_as_editor()
auto preference_item = new wxMenuItem(parent_menu, ConfigMenuPreferences + config_id_base, _L("Preferences") + "\t" + ctrl + "P", ""); auto preference_item = new wxMenuItem(parent_menu, ConfigMenuPreferences + config_id_base, _L("Preferences") + "\t" + ctrl + "P", "");
#endif #endif
//auto printer_item = new wxMenuItem(parent_menu, ConfigMenuPrinter + config_id_base, _L("Printer"), "");
//auto language_item = new wxMenuItem(parent_menu, ConfigMenuLanguage + config_id_base, _L("Switch Language"), "");
// parent_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) {
// switch (event.GetId() - config_id_base) {
// //case ConfigMenuLanguage:
// //{
// // /* Before change application language, let's check unsaved changes on 3D-Scene
// // * and draw user's attention to the application restarting after a language change
// // */
// // {
// // // the dialog needs to be destroyed before the call to switch_language()
// // // or sometimes the application crashes into wxDialogBase() destructor
// // // so we put it into an inner scope
// // wxString title = _L("Language selection");
// // wxMessageDialog dialog(nullptr,
// // _L("Switching the language requires application restart.\n") + "\n\n" +
// // _L("Do you want to continue?"),
// // title,
// // wxICON_QUESTION | wxOK | wxCANCEL);
// // if (dialog.ShowModal() == wxID_CANCEL)
// // return;
// // }
//
// // wxGetApp().switch_language();
// // break;
// //}
// //case ConfigMenuWizard:
// //{
// // wxGetApp().run_wizard(ConfigWizard::RR_USER);
// // break;
// //}
// case ConfigMenuPrinter:
// {
// wxGetApp().params_dialog()->Popup();
// wxGetApp().get_tab(Preset::TYPE_PRINTER)->restore_last_select_item();
// break;
// }
// case ConfigMenuPreferences:
// {
// CallAfter([this] {
// PreferencesDialog dlg(this);
// dlg.ShowModal();
//#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
//#else
// if (dlg.seq_top_layer_only_changed())
//#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// plater()->refresh_print();
//#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
//#ifdef _WIN32
// /*
// if (wxGetApp().app_config()->get("associate_3mf") == "true")
// wxGetApp().associate_3mf_files();
// if (wxGetApp().app_config()->get("associate_stl") == "true")
// wxGetApp().associate_stl_files();
// /*if (wxGetApp().app_config()->get("associate_step") == "true")
// wxGetApp().associate_step_files();*/
//#endif // _WIN32
//#endif
// });
// break;
// }
// default:
// break;
// }
// });
#ifdef __APPLE__ #ifdef __APPLE__
wxString about_title = wxString::Format(_L("&About %s"), SLIC3R_APP_FULL_NAME); wxString about_title = wxString::Format(_L("&About %s"), SLIC3R_APP_FULL_NAME);
//auto about_item = new wxMenuItem(parent_menu, Snapmaker_OrcaMenuAbout + bambu_studio_id_base, about_title, "");
//parent_menu->Bind(wxEVT_MENU, [this, bambu_studio_id_base](wxEvent& event) {
// switch (event.GetId() - bambu_studio_id_base) {
// case Snapmaker_OrcaMenuAbout:
// Slic3r::GUI::about();
// break;
// case Snapmaker_OrcaMenuPreferences:
// CallAfter([this] {
// PreferencesDialog dlg(this);
// dlg.ShowModal();
//#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
//#else
// if (dlg.seq_top_layer_only_changed())
//#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// plater()->refresh_print();
// });
// break;
// default:
// break;
// }
//});
//parent_menu->Insert(0, about_item);
append_menu_item( append_menu_item(
parent_menu, wxID_ANY, _L(about_title), "", parent_menu, wxID_ANY, _L(about_title), "",
[this](wxCommandEvent &) { Slic3r::GUI::about();}, [this](wxCommandEvent &) { Slic3r::GUI::about();},
@@ -2969,10 +2875,7 @@ void MainFrame::init_menubar_as_editor()
plater()->get_current_canvas3D()->force_set_focus(); plater()->get_current_canvas3D()->force_set_focus();
}, },
"", nullptr, []() { return true; }, this); "", nullptr, []() { return true; }, this);
//m_topbar->AddDropDownMenuItem(preference_item);
//m_topbar->AddDropDownMenuItem(printer_item);
//m_topbar->AddDropDownMenuItem(language_item);
//m_topbar->AddDropDownMenuItem(config_item);
m_topbar->AddDropDownSubMenu(helpMenu, _L("Help")); m_topbar->AddDropDownSubMenu(helpMenu, _L("Help"));
// SoftFever calibrations // SoftFever calibrations
@@ -4014,6 +3917,56 @@ void MainFrame::RunScript(wxString js)
m_webview->RunScript(js); m_webview->RunScript(js);
} }
void MainFrame::downloadOpenProject(const std::string& fileUrl, const std::string& fileName, std::string completeFilePath)
{
// std::string fileUrl = "https://public.resource.snapmaker.com/model/public/3mf/test_for_download.3mf";
// std::string filename = "test_for_download.3mf";
wxString fileNameEx = wxString::FromUTF8(fileName.c_str()).Lower();
std::string releaFileName = "";
bool strRes = fileNameEx.EndsWith(".3mf");
if (strRes)
releaFileName = fileName;
else
releaFileName = fileName + ".3mf";
GenericDownloadDialog dlg(_L("downloading the model"), fileUrl, releaFileName, completeFilePath);
auto res = dlg.ShowModal();
if (res != wxID_OK)
return;
if (completeFilePath.empty()) {
auto downloadPath = wxGetApp().app_config->get("download_path");
completeFilePath = downloadPath + "/" + releaFileName;
}
if (!boost::filesystem::exists(completeFilePath))
{
BOOST_LOG_TRIVIAL(warning) << boost::format("the file '%1%' not exists") % completeFilePath;
return;
}
// Auto-open project if it's a .3mf file
boost::filesystem::path path(completeFilePath);
std::string extension = boost::algorithm::to_lower_copy(path.extension().string());
if (extension == ".3mf") {
BOOST_LOG_TRIVIAL(info) << boost::format("GenericDownloadDialog: Auto-opening project file '%1%'") % completeFilePath;
wxString wx_file_path = wxString::FromUTF8(completeFilePath.c_str());
if (wxGetApp().can_load_project() && wxGetApp().mainframe && wxGetApp().mainframe->plater()) {
wxGetApp().mainframe->plater()->load_project(wx_file_path);
}
}
else
{
// Not a valid 3mf file, show error message
wxString msg = wxString::Format(_L("The downloaded file '%s' is not a valid 3MF project file."), releaFileName);
MessageDialog(this, msg, _L("Invalid File"), wxOK | wxICON_WARNING).ShowModal();
}
}
void MainFrame::technology_changed() void MainFrame::technology_changed()
{ {
// update menu titles // update menu titles
+5 -1
View File
@@ -348,7 +348,11 @@ public:
void load_printer_url(); void load_printer_url();
bool is_printer_view() const; bool is_printer_view() const;
void refresh_plugin_tips(); void refresh_plugin_tips();
void RunScript(wxString js); void RunScript(wxString js);
void downloadOpenProject(const std::string& fileUrl,
const std::string& fileName,
std::string completeFilePath = "");
//SoftFever //SoftFever
void show_device(bool bBBLPrinter); void show_device(bool bBBLPrinter);
+18 -2
View File
@@ -146,8 +146,15 @@ bool MarkdownTip::ShowTip(wxPoint pos, std::string const &tip, std::string const
if (_tipView->GetParent() == this) { if (_tipView->GetParent() == this) {
wxSize size = wxDisplay(this).GetClientArea().GetSize(); wxSize size = wxDisplay(this).GetClientArea().GetSize();
_requestPos = pos; _requestPos = pos;
// Ensure display size is valid
if (size.y <= 0) size.y = 600;
if (pos.y + this->GetSize().y > size.y) if (pos.y + this->GetSize().y > size.y)
pos.y = size.y - this->GetSize().y; {
pos.y = 0;
if( (size.y - this->GetSize().y) > 0)
pos.y = (size.y - this->GetSize().y);
}
this->SetPosition(pos); this->SetPosition(pos);
if (tipChanged || _hide) { if (tipChanged || _hide) {
_hide = false; _hide = false;
@@ -258,11 +265,20 @@ void MarkdownTip::OnTitleChanged(wxWebViewEvent& event)
_lastHeight = height; _lastHeight = height;
height *= 1.25; height += 50; height *= 1.25; height += 50;
wxSize size = wxDisplay(this).GetClientArea().GetSize(); wxSize size = wxDisplay(this).GetClientArea().GetSize();
// Ensure display size is valid
if (size.y <= 0) size.y = 600;
if (height > size.y) if (height > size.y)
height = size.y; height = size.y;
wxPoint pos = _requestPos; wxPoint pos = _requestPos;
if (pos.y + height > size.y) if (pos.y + height > size.y)
pos.y = size.y - height; {
pos.y= 0;
if(size.y-height>0)
pos.y = size.y - height;
}
// Ensure height is valid
if (height <= 0) height = 100;
this->SetSize({ 400, (int)height }); this->SetSize({ 400, (int)height });
this->SetPosition(pos); this->SetPosition(pos);
} }
+3
View File
@@ -554,6 +554,9 @@ void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode *node)
scaled_bitmap_name += std::to_string(vol_type); scaled_bitmap_name += std::to_string(vol_type);
scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : "-lm"); scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : "-lm");
if (!m_bitmap_cache)
return;
wxBitmap* bmp = m_bitmap_cache->find(scaled_bitmap_name); wxBitmap* bmp = m_bitmap_cache->find(scaled_bitmap_name);
if (bmp == nullptr) { if (bmp == nullptr) {
std::vector<wxBitmap> bmps; std::vector<wxBitmap> bmps;
-42
View File
@@ -16693,18 +16693,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
islegal = (c_preset == connect_preset); islegal = (c_preset == connect_preset);
/* if (!islegal) {
MessageDialog msg_window(nullptr,
_L(" Your connected machine is ") + (connect_preset == "" ? "Unknown" : connect_preset) + _L("\nYour model's preset is ") + c_preset + _L("\nDo you want to continue?"),
L("machine check"),
wxICON_QUESTION | wxOK);
int res = msg_window.ShowModal();
if (res != wxID_OK) {
return;
}
}*/
DynamicPrintConfig* physical_printer_config = &Slic3r::GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; DynamicPrintConfig* physical_printer_config = &Slic3r::GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (! physical_printer_config || p->model.objects.empty()) if (! physical_printer_config || p->model.objects.empty())
return; return;
@@ -16723,34 +16711,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
local_name.erase(std::remove(local_name.begin(), local_name.end(), '('), local_name.end()); local_name.erase(std::remove(local_name.begin(), local_name.end(), '('), local_name.end());
local_name.erase(std::remove(local_name.begin(), local_name.end(), ')'), local_name.end()); local_name.erase(std::remove(local_name.begin(), local_name.end(), ')'), local_name.end());
/*if (wxGetApp().app_config->get("use_new_connect") == "true") {
upload_job = PrintHostJob(wxGetApp().get_host_config());
} */
// if (local_name == "Snapmaker U1 0.4 nozzle" && devices.size() == 0) {
// MessageDialog msg_window(nullptr, _L("You don't have active machine, do you want to add one?"), _L("Info"), wxICON_QUESTION | wxOK | wxCANCEL);
// int res = msg_window.ShowModal();
// if (res == wxID_OK) {
// wxGetApp().mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
// auto view = wxGetApp().mainframe->m_printer_view;
// if (view) {
// json msg;
// msg["head"] = json::object();
// json payload = json::object();
// payload["cmd"] = "devicepage_add_device";
// payload["method"] = "call_flutter";
// payload["params"] = json::object();
// msg["payload"] = payload;
// std::string str_msg = msg.dump(4, ' ', true);
// view->sendMessage(str_msg);
// }
// }
// return;
// }
if (wxGetApp().app_config->get("use_new_connect") == "true" || local_name == "Snapmaker U1 0.4 nozzle") { if (wxGetApp().app_config->get("use_new_connect") == "true" || local_name == "Snapmaker U1 0.4 nozzle") {
// 先不创建job,直接创建上传 / 上传下载对话框 // 先不创建job,直接创建上传 / 上传下载对话框
// 获取默认文件名 // 获取默认文件名
@@ -16812,8 +16772,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
dialog->set_display_file_name(upload_job.upload_data.upload_path.string()); dialog->set_display_file_name(upload_job.upload_data.upload_path.string());
bool res = dialog->run(); bool res = dialog->run();
// wxGetApp().mainframe->m_printer_view->reload();
if (dialog->is_finish()) { if (dialog->is_finish()) {
wxGetApp().mainframe->select_tab(MainFrame::TabPosition::tpMonitor); wxGetApp().mainframe->select_tab(MainFrame::TabPosition::tpMonitor);
} }
+3
View File
@@ -205,6 +205,9 @@ void PresetComboBox::update_selection()
// A workaround for a set of issues related to text fitting into gtk widgets: // A workaround for a set of issues related to text fitting into gtk widgets:
#if defined(__WXGTK20__) || defined(__WXGTK3__) #if defined(__WXGTK20__) || defined(__WXGTK3__)
// Guard: m_widget may not be a GtkCellLayout (e.g. not yet realized, or GtkComboBoxText in Flatpak/GTK3)
if (!m_widget || !GTK_IS_CELL_LAYOUT(m_widget))
return;
GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_widget)); GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_widget));
// 'cells' contains the GtkCellRendererPixBuf for the icon, // 'cells' contains the GtkCellRendererPixBuf for the icon,
+4 -15
View File
@@ -28,8 +28,10 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
wxString url = wxString::FromUTF8(LOCALHOST_URL + std::to_string(PAGE_HTTP_PORT) + "/web/flutter_web/index.html?path=2");
auto real_url = wxGetApp().get_international_url(url);
// Create the webview // Create the webview
m_browser = WebView::CreateWebView(this, ""); m_browser = WebView::CreateWebView(this, real_url);
if (m_browser == nullptr) { if (m_browser == nullptr) {
wxLogError("Could not init m_browser"); wxLogError("Could not init m_browser");
return; return;
@@ -45,16 +47,6 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
update_mode(); update_mode();
// Log backend information
/* m_browser->GetUserAgent() may lead crash
if (wxGetApp().get_mode() == comDevelop) {
wxLogMessage(wxWebView::GetBackendVersionInfo().ToString());
wxLogMessage("Backend: %s Version: %s", m_browser->GetClassInfo()->GetClassName(),
wxWebView::GetBackendVersionInfo().ToString());
wxLogMessage("User Agent: %s", m_browser->GetUserAgent());
}
*/
//Zoom //Zoom
m_zoomFactor = 100; m_zoomFactor = 100;
@@ -77,8 +69,6 @@ PrinterWebView::~PrinterWebView()
void PrinterWebView::load_url(wxString& url, wxString apikey) void PrinterWebView::load_url(wxString& url, wxString apikey)
{ {
// this->Show();
// this->Raise();
if (m_browser == nullptr) if (m_browser == nullptr)
return; return;
m_apikey = apikey; m_apikey = apikey;
@@ -90,10 +80,9 @@ void PrinterWebView::load_url(wxString& url, wxString apikey)
wxGetApp().fltviews().remove_printer_view(this); wxGetApp().fltviews().remove_printer_view(this);
} }
m_browser->Show();
m_browser->LoadURL(url); m_browser->LoadURL(url);
m_browser->Show();
//m_browser->SetFocus();
UpdateState(); UpdateState();
} }
+100 -37
View File
@@ -2,7 +2,7 @@
#include "SSWCP.hpp" #include "SSWCP.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "WCPDownloadManager.hpp" #include "DownloadManager.hpp"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "slic3r/GUI/Tab.hpp" #include "slic3r/GUI/Tab.hpp"
#include "sentry_wrapper/SentryWrapper.hpp" #include "sentry_wrapper/SentryWrapper.hpp"
@@ -3005,10 +3005,11 @@ void SSWCP_MachineOption_Instance::sw_FinishFilamentMapping()
if (wxGetApp().get_web_preprint_dialog()) { if (wxGetApp().get_web_preprint_dialog()) {
WebPreprintDialog* dialog = dynamic_cast<WebPreprintDialog*>(wxGetApp().get_web_preprint_dialog()); WebPreprintDialog* dialog = dynamic_cast<WebPreprintDialog*>(wxGetApp().get_web_preprint_dialog());
if (dialog) { if (dialog) {
// BBS: Use SafeEndModal to prevent duplicate EndModal calls
if(dialog->is_finish()){ if(dialog->is_finish()){
dialog->EndModal(wxID_OK); dialog->SafeEndModal(wxID_OK);
}else{ }else{
dialog->EndModal(wxID_CANCEL); dialog->SafeEndModal(wxID_CANCEL);
} }
} }
} }
@@ -3058,29 +3059,19 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping()
long long res = 0; long long res = 0;
if ((oriclr.size() != 7 && oriclr.size() != 9) || oriclr[0] != '#') { if ((oriclr.size() != 7 && oriclr.size() != 9) || oriclr[0] != '#') {
return -1; return 0;
} }
if (oriclr.size() == 7) { auto colorSize = oriclr.size();//7 or 9
for (int i = 1; i <= 6; ++i) { for (auto i = 1; i < colorSize; i++)
if (oriclr[7 - i] - '0' >= 0 && oriclr[7 - i] - '0' <= 9) { {
res += std::pow(16, i - 1) * (oriclr[7 - i] - '0'); if (oriclr[colorSize - i] - '0' >= 0 && oriclr[colorSize - i] - '0' <= 9) {
} else { res += std::pow(16, i - 1) * (oriclr[colorSize - i] - '0');
res += std::pow(16, i - 1) * (oriclr[7 - i] - 'A' + 10); } else {
} res += std::pow(16, i - 1) * (oriclr[colorSize - i] - 'A' + 10);
}
} else {
for (int i = 1; i <= 8; ++i) {
if (oriclr[7 - i] - '0' >= 0 && oriclr[7 - i] - '0' <= 9) {
res += std::pow(16, i - 1) * (oriclr[7 - i] - '0');
} else {
res += std::pow(16, i - 1) * (oriclr[7 - i] - 'A' + 10);
}
} }
} }
return res; return res;
}; };
@@ -3190,14 +3181,10 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping()
response["thumbnails"] = thumbnails; response["thumbnails"] = thumbnails;
// file name // file name
response["filename"] = SSWCP::get_display_filename(); response["filename"] = SSWCP::get_display_filename();
response["filepath"] = SSWCP::get_active_filename(); response["filepath"] = SSWCP::get_active_filename();
m_res_data = response; m_res_data = response;
send_to_js(); send_to_js();
finish_job(); finish_job();
@@ -4304,6 +4291,8 @@ void SSWCP_UserLogin_Instance::process()
sw_GetUserUpdatePrivacy(); sw_GetUserUpdatePrivacy();
} else if (m_cmd == DOWNLOAD_FILE) { } else if (m_cmd == DOWNLOAD_FILE) {
sw_DownloadFile(); sw_DownloadFile();
} else if (m_cmd == DOWNLOAD_FILE_AND_OPEN) {
sw_DownloadFileAndOpen();
} else if (m_cmd == CANCEL_DOWNLOAD) { } else if (m_cmd == CANCEL_DOWNLOAD) {
sw_CancelDownload(); sw_CancelDownload();
} else if (m_cmd == FILE_VIEW) { } else if (m_cmd == FILE_VIEW) {
@@ -4390,7 +4379,52 @@ void SSWCP_UserLogin_Instance::sw_GetUserUpdatePrivacy()
} }
void SSWCP_UserLogin_Instance::sw_DownloadFile() { void SSWCP_UserLogin_Instance::sw_DownloadFileAndOpen()
{
try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
if (fileUrl.empty() || fileName.empty()) {
handle_general_fail(-1, wxString::FromUTF8("file_url and file_name are required"));
return;
}
// Use Download Manager
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, wxString::FromUTF8("Download Manager not available"));
return;
}
// WebView script message runs inside the webview event handler; calling ShowModal() synchronously
// (via GenericDownloadDialog in downloadOpenProject) causes re-entrancy / crashes on Windows.
// Defer to the next event-loop iteration — same pattern as sw_UserLogin().
std::shared_ptr<SSWCP_UserLogin_Instance> self =
std::static_pointer_cast<SSWCP_UserLogin_Instance>(shared_from_this());
wxGetApp().CallAfter([self, fileUrl, fileName]() {
if (!wxGetApp().mainframe) {
self->handle_general_fail(-1, wxString::FromUTF8("Main window not available"));
return;
}
try {
wxGetApp().mainframe->downloadOpenProject(fileUrl, fileName, "");
self->m_status = 0;
self->m_msg = "success";
self->send_to_js();
self->finish_job();
} catch (const std::exception& e) {
self->handle_general_fail(-1, wxString::FromUTF8(e.what()));
}
});
} catch (const std::exception& e) {
handle_general_fail(-1, wxString::FromUTF8(e.what()));
}
}
void SSWCP_UserLogin_Instance::sw_DownloadFile()
{
try { try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : ""; std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : ""; std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
@@ -4400,27 +4434,55 @@ void SSWCP_UserLogin_Instance::sw_DownloadFile() {
return; return;
} }
// Use WCP Download Manager // Use Download Manager
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager(); DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) { if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available"); handle_general_fail(-1, "Download Manager not available");
return; return;
} }
// Start download task //only download file and don't do anything.
size_t task_id = download_mgr->start_download(fileUrl, fileName, shared_from_this()); //wxGetApp().mainframe->downloadOpenProject(fileUrl, fileName, "");
m_status = 0;
m_msg = "success";
send_to_js();
finish_job();
} catch (std::exception& e) {
handle_general_fail(-1, e.what());
}
}
void SSWCP_UserLogin_Instance::sw_DownloadFileEx() {
try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
if (fileUrl.empty() || fileName.empty()) {
handle_general_fail(-1, "file_url and file_name are required");
return;
}
// Use Download Manager
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, "Download Manager not available");
return;
}
size_t task_id = download_mgr->start_wcp_download(fileUrl,
fileName,
shared_from_this(),
true);
// Return task ID to Flutter
json response; json response;
response["task_id"] = task_id; response["task_id"] = task_id;
response["file_name"] = fileName; response["file_name"] = fileName;
response["file_url"] = fileUrl; response["file_url"] = fileUrl;
m_res_data = response; m_res_data = response;
m_status = 0; m_status = 0;
m_msg = "Download started"; m_msg = "success";
send_to_js(); send_to_js();
// Note: Do not call finish_job() here, as download is asynchronous
// The manager will send progress updates and completion/error messages via WCP
} catch (std::exception& e) { } catch (std::exception& e) {
handle_general_fail(-1, e.what()); handle_general_fail(-1, e.what());
@@ -4436,7 +4498,7 @@ void SSWCP_UserLogin_Instance::sw_CancelDownload() {
return; return;
} }
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager(); DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) { if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available"); handle_general_fail(-1, "WCP Download Manager not available");
return; return;
@@ -5996,7 +6058,8 @@ std::unordered_set<std::string> SSWCP::m_project_cmd_list = {
}; };
std::unordered_set<std::string> SSWCP::m_login_cmd_list = {"sw_UserLogin", "sw_UserLogout", "sw_GetUserLoginState", "sw_SubscribeUserLoginState", std::unordered_set<std::string> SSWCP::m_login_cmd_list = {"sw_UserLogin", "sw_UserLogout", "sw_GetUserLoginState", "sw_SubscribeUserLoginState",
UPDATE_PRIVACY_STATUS, GET_PRIVACY_STATUS}; UPDATE_PRIVACY_STATUS, GET_PRIVACY_STATUS,
DOWNLOAD_FILE,FILE_VIEW, CANCEL_DOWNLOAD, DOWNLOAD_FILE_AND_OPEN};
std::unordered_set<std::string> SSWCP::m_machine_manage_cmd_list = { std::unordered_set<std::string> SSWCP::m_machine_manage_cmd_list = {
"sw_GetLocalDevices", "sw_AddDevice", "sw_SubscribeLocalDevices", "sw_RenameDevice", "sw_SwitchModel", "sw_DeleteDevices" "sw_GetLocalDevices", "sw_AddDevice", "sw_SubscribeLocalDevices", "sw_RenameDevice", "sw_SwitchModel", "sw_DeleteDevices"
+6
View File
@@ -31,6 +31,7 @@ using tcp = asio::ip::tcp;
#define DELETE_CAMERA_TIMELAPSE "sw_DeleteCameraTimelapse" #define DELETE_CAMERA_TIMELAPSE "sw_DeleteCameraTimelapse"
#define GET_DEVICEDATA_STORAGESPACE "sw_GetDeviceDataStorageSpace" #define GET_DEVICEDATA_STORAGESPACE "sw_GetDeviceDataStorageSpace"
#define DOWNLOAD_FILE "sw_DownloadFile" #define DOWNLOAD_FILE "sw_DownloadFile"
#define DOWNLOAD_FILE_AND_OPEN "sw_DownLoadFileAndOpen"
#define CANCEL_DOWNLOAD "sw_CancelDownload" #define CANCEL_DOWNLOAD "sw_CancelDownload"
#define FILE_VIEW "sw_FileView" #define FILE_VIEW "sw_FileView"
@@ -541,6 +542,11 @@ private:
void sw_SubUserUpdatePrivacy(); void sw_SubUserUpdatePrivacy();
void sw_DownloadFile(); void sw_DownloadFile();
void sw_DownloadFileAndOpen();
void sw_DownloadFileEx();
void sw_CancelDownload(); void sw_CancelDownload();
void sw_FileView(); void sw_FileView();
-276
View File
@@ -1,276 +0,0 @@
#include "WCPDownloadManager.hpp"
#include "GUI_App.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace GUI {
size_t WCPDownloadManager::start_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
// Get download path
auto downloadPath = wxGetApp().app_config->get("download_path");
boost::filesystem::path dest_folder(downloadPath);
boost::filesystem::create_directories(dest_folder);
boost::filesystem::path dest_file = dest_folder / file_name;
std::string dest_path = dest_file.string();
// Create task
auto task = std::make_shared<WCPDownloadTask>(task_id, file_url, file_name, dest_path, wcp_instance);
task->state = WCPDownloadState::Downloading;
m_tasks[task_id] = task;
// Start download
wxGetApp().CallAfter([this, task]() {
try {
// Step 1: Create Http object
Http http = Http::get(task->file_url);
// Step 2: Set progress callback
http.on_progress([this, task](Http::Progress progress, bool& cancel) {
if (task->state == WCPDownloadState::Canceled) {
cancel = true;
return;
}
// Calculate progress
int percent = 0;
if (progress.dltotal > 0) {
percent = (int)(progress.dlnow * 100 / progress.dltotal);
}
task->percent = percent;
// Throttle progress updates: update every 5% or every second
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto& last_pct = m_last_percent[task->task_id];
auto& last_upd = m_last_update[task->task_id];
auto now = std::chrono::steady_clock::now();
bool should_update = false;
if (percent - last_pct >= 5) {
should_update = true;
last_pct = percent;
} else if (now - last_upd >= std::chrono::seconds(1)) {
should_update = true;
}
if (should_update) {
last_upd = now;
wxGetApp().CallAfter([this, task, percent, progress]() {
send_progress_update(task, percent, progress.dlnow, progress.dltotal);
});
}
});
// Step 3: Set complete callback
http.on_complete([this, task](std::string body, unsigned status) {
wxGetApp().CallAfter([this, task, body]() {
try {
// Save file
boost::nowide::ofstream file(task->dest_path, std::ios::binary);
if (!file.is_open()) {
send_error_update(task, "Failed to open file for writing");
cleanup_task(task->task_id);
return;
}
file.write(body.c_str(), body.size());
file.close();
task->state = WCPDownloadState::Completed;
task->percent = 100;
send_complete_update(task, task->dest_path);
cleanup_task(task->task_id);
} catch (std::exception& e) {
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
});
// Step 4: Set error callback
http.on_error([this, task](std::string body, std::string error, unsigned status) {
wxGetApp().CallAfter([this, task, error, status]() {
task->state = WCPDownloadState::Error;
task->error_message = error;
send_error_update(task, error);
cleanup_task(task->task_id);
});
});
// Step 5: Start download and save Http::Ptr for cancellation
task->http_object = http.perform();
} catch (std::exception& e) {
task->state = WCPDownloadState::Error;
task->error_message = e.what();
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
return task_id;
}
bool WCPDownloadManager::cancel_download(size_t task_id) {
std::shared_ptr<SSWCP_Instance> wcp_to_destroy;
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it == m_tasks.end()) {
return false;
}
auto task = it->second;
if (task->state == WCPDownloadState::Downloading) {
task->state = WCPDownloadState::Canceled;
if (task->http_object) {
task->http_object->cancel();
}
// Get WCP instance before cleanup (for destruction after lock release)
wcp_to_destroy = task->wcp_instance.lock();
cleanup_task(task_id);
} else {
return false;
}
}
// Destroy WCP instance outside the lock to prevent deadlock
// This is the WCP instance from the original download request (sw_DownloadFile)
if (wcp_to_destroy) {
wcp_to_destroy->finish_job();
}
return true;
}
bool WCPDownloadManager::pause_download(size_t task_id) {
// Pause functionality can be implemented if needed
// Current Http module may not support pause, need to implement resume from breakpoint
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == WCPDownloadState::Downloading) {
it->second->state = WCPDownloadState::Paused;
// Note: Http module doesn't support pause directly, would need breakpoint resume
return true;
}
return false;
}
bool WCPDownloadManager::resume_download(size_t task_id) {
// Resume functionality can be implemented if needed
// Would require breakpoint resume support in Http module
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == WCPDownloadState::Paused) {
// Would need to restart download with range header
return false; // Not implemented yet
}
return false;
}
WCPDownloadState WCPDownloadManager::get_task_state(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second->state;
}
return WCPDownloadState::Error;
}
std::shared_ptr<WCPDownloadTask> WCPDownloadManager::get_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second;
}
return nullptr;
}
void WCPDownloadManager::send_progress_update(std::shared_ptr<WCPDownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (auto wcp = task->wcp_instance.lock()) {
json progress_data;
progress_data["task_id"] = task->task_id;
progress_data["percent"] = percent;
progress_data["downloaded"] = downloaded;
progress_data["total"] = total;
progress_data["state"] = "downloading";
wcp->m_res_data = progress_data;
wcp->m_status = 0;
wcp->m_msg = "Download progress";
// Use progress event ID
json header;
header["event_id"] = wcp->m_event_id + "_progress";
header["command"] = "download_progress";
wcp->m_header = header;
wcp->send_to_js();
}
}
void WCPDownloadManager::send_complete_update(std::shared_ptr<WCPDownloadTask> task,
const std::string& file_path) {
if (auto wcp = task->wcp_instance.lock()) {
json complete_data;
complete_data["task_id"] = task->task_id;
complete_data["file_path"] = file_path;
complete_data["file_name"] = task->file_name;
complete_data["percent"] = 100;
complete_data["state"] = "completed";
wcp->m_res_data = complete_data;
wcp->m_status = 0;
wcp->m_msg = "Download completed";
wcp->send_to_js();
// Release WCP instance to prevent memory leak
wcp->finish_job();
}
}
void WCPDownloadManager::send_error_update(std::shared_ptr<WCPDownloadTask> task,
const std::string& error) {
if (auto wcp = task->wcp_instance.lock()) {
json error_data;
error_data["task_id"] = task->task_id;
error_data["error"] = error;
error_data["state"] = "error";
wcp->m_res_data = error_data;
wcp->m_status = -1;
wcp->m_msg = error;
wcp->send_to_js();
// Release WCP instance to prevent memory leak
wcp->finish_job();
}
}
void WCPDownloadManager::cleanup_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
}
}} // namespace Slic3r::GUI
-104
View File
@@ -1,104 +0,0 @@
#ifndef slic3r_WCPDownloadManager_hpp_
#define slic3r_WCPDownloadManager_hpp_
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <chrono>
#include "../Utils/Http.hpp"
#include "SSWCP.hpp"
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp"
namespace Slic3r { namespace GUI {
// Download task state
enum class WCPDownloadState {
Pending,
Downloading,
Paused,
Completed,
Error,
Canceled
};
// Download task information
struct WCPDownloadTask {
size_t task_id;
std::string file_url;
std::string file_name;
std::string dest_path;
std::weak_ptr<SSWCP_Instance> wcp_instance; // Associated WCP instance
Http::Ptr http_object; // HTTP object for cancellation
WCPDownloadState state;
int percent;
std::string error_message;
WCPDownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, std::shared_ptr<SSWCP_Instance> instance)
: task_id(id), file_url(url), file_name(name), dest_path(path),
wcp_instance(instance), state(WCPDownloadState::Pending), percent(0) {}
};
// WCP Download Manager
class WCPDownloadManager {
public:
static WCPDownloadManager& getInstance() {
static WCPDownloadManager instance;
return instance;
}
// Start a download task
size_t start_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance);
// Cancel a download task
bool cancel_download(size_t task_id);
// Pause a download task (if needed)
bool pause_download(size_t task_id);
// Resume a download task (if needed)
bool resume_download(size_t task_id);
// Get task state
WCPDownloadState get_task_state(size_t task_id);
// Get task information
std::shared_ptr<WCPDownloadTask> get_task(size_t task_id);
private:
WCPDownloadManager() = default;
~WCPDownloadManager() = default;
WCPDownloadManager(const WCPDownloadManager&) = delete;
WCPDownloadManager& operator=(const WCPDownloadManager&) = delete;
std::mutex m_tasks_mutex;
std::unordered_map<size_t, std::shared_ptr<WCPDownloadTask>> m_tasks;
std::atomic<size_t> m_next_task_id{1};
// Track last progress update for throttling
std::unordered_map<size_t, int> m_last_percent;
std::unordered_map<size_t, std::chrono::steady_clock::time_point> m_last_update;
// Send progress update to WCP
void send_progress_update(std::shared_ptr<WCPDownloadTask> task, int percent,
size_t downloaded, size_t total);
// Send completion message to WCP
void send_complete_update(std::shared_ptr<WCPDownloadTask> task, const std::string& file_path);
// Send error message to WCP
void send_error_update(std::shared_ptr<WCPDownloadTask> task, const std::string& error);
// Clean up completed task
void cleanup_task(size_t task_id);
};
}} // namespace Slic3r::GUI
#endif // slic3r_WCPDownloadManager_hpp_
+38 -6
View File
@@ -34,7 +34,7 @@ WebPreprintDialog::WebPreprintDialog()
wxLogError("Could not init m_browser"); wxLogError("Could not init m_browser");
return; return;
} }
m_browser->Hide(); //m_browser->Hide();
// Connect the webview events // Connect the webview events
Bind(wxEVT_WEBVIEW_NAVIGATING, &WebPreprintDialog::OnNavigationRequest, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_NAVIGATING, &WebPreprintDialog::OnNavigationRequest, this, m_browser->GetId());
@@ -96,6 +96,22 @@ void WebPreprintDialog::set_display_file_name(const std::string& filename) {
void WebPreprintDialog::set_gcode_file_name(const std::string& filename) void WebPreprintDialog::set_gcode_file_name(const std::string& filename)
{ m_gcode_file_name = filename; } { m_gcode_file_name = filename; }
void WebPreprintDialog::set_finish(bool flag)
{
m_finish = flag;
// BBS: Don't call EndModal here to avoid conflict with sw_FinishFilamentMapping()
// The external sw_FinishFilamentMapping() function will handle EndModal based on m_finish flag
}
void WebPreprintDialog::SafeEndModal(int returnCode)
{
// BBS: Prevent duplicate EndModal calls which can cause crashes
if (IsModal() && !m_modal_ended) {
m_modal_ended = true;
EndModal(returnCode);
}
}
void WebPreprintDialog::reload() void WebPreprintDialog::reload()
{ {
load_url(m_prePrint_url); load_url(m_prePrint_url);
@@ -104,9 +120,9 @@ void WebPreprintDialog::reload()
void WebPreprintDialog::load_url(wxString &url) void WebPreprintDialog::load_url(wxString &url)
{ {
wxGetApp().fltviews().add_view(m_browser, url); wxGetApp().fltviews().add_view(m_browser, url);
m_browser->LoadURL(url);
m_browser->Show(); m_browser->Show();
m_browser->LoadURL(url);
Layout(); Layout();
} }
@@ -123,8 +139,16 @@ bool WebPreprintDialog::run()
} }
this->load_url(real_url); this->load_url(real_url);
if (this->ShowModal() == wxID_OK) {
return true; // BBS: Reset flags before showing modal
m_finish = false;
m_modal_ended = false;
int result = this->ShowModal();
// BBS: Check finish flag to determine return value
if (result == wxID_OK || (result == wxID_CANCEL && m_finish)) {
return m_finish;
} }
return false; return false;
} }
@@ -186,7 +210,15 @@ void WebPreprintDialog::OnClose(wxCloseEvent& evt)
{ {
auto noti_manager = wxGetApp().mainframe->plater()->get_notification_manager(); auto noti_manager = wxGetApp().mainframe->plater()->get_notification_manager();
noti_manager->close_notification_of_type(NotificationType::PrintHostUpload); noti_manager->close_notification_of_type(NotificationType::PrintHostUpload);
evt.Skip();
// BBS: Use SafeEndModal to prevent duplicate EndModal calls
// This ensures consistency with sw_FinishFilamentMapping() and prevents crashes
SafeEndModal(wxID_CANCEL);
// If not modal or already ended, skip the event
if (!IsModal() || m_modal_ended) {
evt.Skip();
}
} }
}} // namespace Slic3r::GUI }} // namespace Slic3r::GUI
+5 -1
View File
@@ -33,7 +33,10 @@ public:
bool is_finish() { return m_finish; } bool is_finish() { return m_finish; }
void set_finish(bool flag) { m_finish = flag; } void set_finish(bool flag);
// BBS: Safely end modal dialog, preventing duplicate EndModal calls
void SafeEndModal(int returnCode);
private: private:
void OnClose(wxCloseEvent& evt); void OnClose(wxCloseEvent& evt);
@@ -53,6 +56,7 @@ private:
bool m_switch_to_device = false; bool m_switch_to_device = false;
bool m_finish = false; bool m_finish = false;
bool m_modal_ended = false; // BBS: Flag to prevent duplicate EndModal calls
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };
+1 -1
View File
@@ -309,7 +309,7 @@ void SMUserLogin::OnScriptMessage(wxWebViewEvent &evt)
} }
else if (strCmd == "get_localhost_url") { else if (strCmd == "get_localhost_url") {
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_localhost_url"; BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_localhost_url";
wxGetApp().start_http_server(); //wxGetApp().start_http_server();
std::string sequence_id = j["sequence_id"].get<std::string>(); std::string sequence_id = j["sequence_id"].get<std::string>();
CallAfter([this, sequence_id] { CallAfter([this, sequence_id] {
json ack_j; json ack_j;
+1 -1
View File
@@ -283,7 +283,7 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
} }
else if (strCmd == "get_localhost_url") { else if (strCmd == "get_localhost_url") {
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_localhost_url"; BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_localhost_url";
wxGetApp().start_http_server(); //wxGetApp().start_http_server();
std::string sequence_id = j["sequence_id"].get<std::string>(); std::string sequence_id = j["sequence_id"].get<std::string>();
CallAfter([this, sequence_id] { CallAfter([this, sequence_id] {
json ack_j; json ack_j;
-5
View File
@@ -253,12 +253,7 @@ void WebViewPanel::reload() {
void WebViewPanel::load_url(wxString& url) void WebViewPanel::load_url(wxString& url)
{ {
this->Show();
this->Raise();
/*m_url->SetLabelText(url);
if (wxGetApp().get_mode() == comDevelop)
wxLogMessage(m_url->GetValue());*/
m_browser->LoadURL(url); m_browser->LoadURL(url);
wxGetApp().fltviews().add_webview_panel(this, url); wxGetApp().fltviews().add_webview_panel(this, url);
+14 -1
View File
@@ -368,7 +368,12 @@ void DropDown::messureSize()
wxWindow::SetSize(szContent); wxWindow::SetSize(szContent);
#ifdef __WXGTK__ #ifdef __WXGTK__
// Gtk has a wrapper window for popup widget // Gtk has a wrapper window for popup widget
gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y); // Fix for GNOME Platform 48 X11 backend: ensure size is valid before calling gtk_window_resize
int gtk_width = szContent.x;
int gtk_height = szContent.y;
if (gtk_width <= 0) gtk_width = 100;
if (gtk_height <= 0) gtk_height = 100;
gtk_window_resize(GTK_WINDOW(m_widget), gtk_width, gtk_height);
#endif #endif
need_sync = false; need_sync = false;
} }
@@ -384,6 +389,10 @@ void DropDown::autoPosition()
size = rowSize; size = rowSize;
size.y *= std::min((size_t)15, texts.size()); size.y *= std::min((size_t)15, texts.size());
size.y += texts.size() > 15 ? rowSize.y / 2 : 0; size.y += texts.size() > 15 ? rowSize.y / 2 : 0;
#ifdef __WXGTK__
if (size.x < 1) size.x = 1;
if (size.y < 1) size.y = 1;
#endif
if (size != GetSize()) { if (size != GetSize()) {
wxWindow::SetSize(size); wxWindow::SetSize(size);
offset = wxPoint(); offset = wxPoint();
@@ -396,6 +405,10 @@ void DropDown::autoPosition()
if (GetPosition().y + size.y + 10 > drect.GetBottom()) { if (GetPosition().y + size.y + 10 > drect.GetBottom()) {
if (use_content_width && texts.size() <= 15) size.x += 6; if (use_content_width && texts.size() <= 15) size.x += 6;
size.y = drect.GetBottom() - GetPosition().y - 10; size.y = drect.GetBottom() - GetPosition().y - 10;
#ifdef __WXGTK__
if (size.y < 1) size.y = 1;
if (size.x < 1) size.x = 1;
#endif
wxWindow::SetSize(size); wxWindow::SetSize(size);
if (selection >= 0) { if (selection >= 0) {
if (offset.y + rowSize.y * (selection + 1) > size.y) if (offset.y + rowSize.y * (selection + 1) > size.y)
+1 -1
View File
@@ -30,7 +30,7 @@ class DropDown : public PopupWindow
wxSize textSize; wxSize textSize;
wxSize iconSize; wxSize iconSize;
wxSize rowSize; wxSize rowSize{100, 30}; // Initialize with default values
StateHandler state_handler; StateHandler state_handler;
StateColor text_color; StateColor text_color;
+15 -2
View File
@@ -17,6 +17,11 @@
#endif #endif
#include "sentry_wrapper/SentryWrapper.hpp" #include "sentry_wrapper/SentryWrapper.hpp"
#if defined(__linux__)
#include <mutex>
#endif
#ifdef __WIN32__ #ifdef __WIN32__
#include <WebView2.h> #include <WebView2.h>
#include <Shellapi.h> #include <Shellapi.h>
@@ -275,10 +280,18 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
// And the memory: file system // And the memory: file system
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory"))); webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#else #else
// With WKWebView handlers need to be registered before creation // Handlers must be registered before Create(). Linux (WebKit2): scheme is process-global, register once to avoid "Cannot register URI scheme ... more than once".
// macOS (WKWebView): scheme is per-view, each WebView needs its own handlers.
#if defined(__linux__)
static std::once_flag s_wxfs_memory_handlers_once;
std::call_once(s_wxfs_memory_handlers_once, [webView]() {
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
});
#else
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs"))); webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
// And the memory: file system
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory"))); webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#endif
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
webView->SetUserAgent(wxString::Format("SM-Slicer/v%s (%s) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)", SLIC3R_VERSION, webView->SetUserAgent(wxString::Format("SM-Slicer/v%s (%s) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)", SLIC3R_VERSION,
Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light")); Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light"));
+77 -73
View File
@@ -232,7 +232,11 @@ struct PresetUpdater::priv
void sync_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch = false, std::string current_version="", std::string changelog_file=""); void sync_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch = false, std::string current_version="", std::string changelog_file="");
void sync_config(bool isAuto_check = true); void sync_config(bool isAuto_check = true);
void sync_update_flutter_resource(bool isAuto_check = true); void sync_update_flutter_resource(bool isAuto_check = true);
bool download_file(const std::string& url, const std::string& target_path, int timeout_sec = 30, bool* cancel_flag = nullptr); bool download_file(const std::string& url,
const std::string& target_path,
const std::string& extract_path,
int timeout_sec = 30,
bool* cancel_flag = nullptr);
void sync_tooltip(std::string http_url, std::string language); void sync_tooltip(std::string http_url, std::string language);
void sync_plugins(std::string http_url, std::string plugin_version); void sync_plugins(std::string http_url, std::string plugin_version);
void sync_printer_config(std::string http_url); void sync_printer_config(std::string http_url);
@@ -320,7 +324,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
{ {
bool res = true; bool res = true;
std::string file_path = source_path.string(); std::string file_path = source_path.string();
std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string(); fs::path parent_path = !dest_path.empty() ? dest_path : source_path.parent_path();
mz_zip_archive archive; mz_zip_archive archive;
mz_zip_zero_struct(&archive); mz_zip_zero_struct(&archive);
@@ -331,6 +335,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
} }
mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
fs::path base_path = parent_path.lexically_normal();
mz_zip_archive_file_stat stat; mz_zip_archive_file_stat stat;
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it // we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
@@ -338,30 +343,48 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
{ {
if (mz_zip_reader_file_stat(&archive, i, &stat)) if (mz_zip_reader_file_stat(&archive, i, &stat))
{ {
std::string dest_file = parent_path+"/"+stat.m_filename; fs::path full_dest = (base_path / stat.m_filename).lexically_normal();
if (stat.m_is_directory) { // Reject paths that escape base (e.g. ".." in zip entry)
fs::path dest_path(dest_file); std::string rel_str = full_dest.lexically_relative(base_path).generic_string();
if (!fs::exists(dest_path)) if (rel_str.empty() || rel_str.find("..") == 0) {
fs::create_directories(dest_path); BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: skip invalid path "<<stat.m_filename;
continue; continue;
} }
else if (stat.m_uncomp_size == 0) { if (stat.m_is_directory) {
if (!fs::exists(full_dest))
fs::create_directories(full_dest);
continue;
}
if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename; BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename;
continue; continue;
} }
try try
{ {
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0); // Ensure parent directory exists (zip often has no directory entries, e.g. "flutter_web/version.json" only)
fs::path parent_dir = full_dest.parent_path();
if (!parent_dir.empty() && !fs::exists(parent_dir))
fs::create_directories(parent_dir);
std::string dest_file_encoded = encode_path(full_dest.string().c_str());
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file_encoded.c_str(), 0);
#ifdef _WIN32
if (!res) { if (!res) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<dest_file<<" failed"; std::wstring dest_file_w = boost::nowide::widen(full_dest.generic_string());
close_zip_reader(&archive); res = mz_zip_reader_extract_to_file_w(&archive, stat.m_file_index, dest_file_w.c_str(), 0);
return res;
} }
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<dest_file; #endif
if (!res) {
mz_zip_error zip_err = mz_zip_get_last_error(&archive);
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<full_dest.string()
<< " failed: " << (zip_err != MZ_ZIP_NO_ERROR ? mz_zip_get_error_string(zip_err) : "unknown");
close_zip_reader(&archive);
return false;
}
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<full_dest.string();
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive); close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what(); BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what();
return false; return false;
@@ -373,7 +396,7 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
} }
close_zip_reader(&archive); close_zip_reader(&archive);
return true; return true;
} }
// Remove leftover paritally downloaded files, if any. // Remove leftover paritally downloaded files, if any.
@@ -657,6 +680,7 @@ void PresetUpdater::priv::sync_resources(std::string http_url, std::map<std::str
} }
bool PresetUpdater::priv::download_file(const std::string& url, bool PresetUpdater::priv::download_file(const std::string& url,
const std::string& target_path, const std::string& target_path,
const std::string& extract_path,
int timeout_sec, int timeout_sec,
bool* cancel_flag ) bool* cancel_flag )
{ {
@@ -676,7 +700,7 @@ bool PresetUpdater::priv::download_file(const std::string& url,
.on_error([&url](std::string body, std::string error, unsigned http_status) { .on_error([&url](std::string body, std::string error, unsigned http_status) {
BOOST_LOG_TRIVIAL(error) << "Download failed: " << url << ", HTTP status: " << http_status << ", error: " << error; BOOST_LOG_TRIVIAL(error) << "Download failed: " << url << ", HTTP status: " << http_status << ", error: " << error;
}) })
.on_complete([&](std::string body, unsigned http_status) { .on_complete([&, target_path,tmp_path,extract_path](std::string body, unsigned http_status) {
if (http_status != 200) { if (http_status != 200) {
BOOST_LOG_TRIVIAL(error) << "Download failed with HTTP status: " << http_status; BOOST_LOG_TRIVIAL(error) << "Download failed with HTTP status: " << http_status;
return; return;
@@ -700,12 +724,12 @@ bool PresetUpdater::priv::download_file(const std::string& url,
BOOST_LOG_TRIVIAL(error) << "Failed to rename temp file: " << ec.message(); BOOST_LOG_TRIVIAL(error) << "Failed to rename temp file: " << ec.message();
return; return;
} }
extract_file(target_path, "../ota/profiles/"); extract_file(target_path, extract_path);
BOOST_LOG_TRIVIAL(info) << "Download completed: " << target_path; BOOST_LOG_TRIVIAL(info) << "Download completed: " << target_path;
res = true;
}) })
.timeout_max(timeout_sec) .timeout_max(timeout_sec)
.perform_sync(); .perform();
if (fs::exists(tmp_path)) { if (fs::exists(tmp_path)) {
fs::remove(tmp_path); fs::remove(tmp_path);
@@ -803,8 +827,19 @@ void PresetUpdater::priv::sync_update_flutter_resource(bool isAuto_check)
return; return;
} }
if (currentPresetVersion < remoteVersion) if (currentPresetVersion < remoteVersion) {
download_file(fileUrl, fileName);
if (fs::exists(fileName))
fs::remove(fileName);
fs::path tmpPath = fileName;
auto dirPath = tmpPath.parent_path() / "profiles/flutter_web";
if (fs::exists(dirPath))
fs::remove_all(dirPath);
download_file(fileUrl, fileName, "../ota/profiles/");
}
else { else {
if (!isAuto_check) { if (!isAuto_check) {
wxCommandEvent* evt = new wxCommandEvent(EVT_NO_WEB_RESOURCE_UPDATE); wxCommandEvent* evt = new wxCommandEvent(EVT_NO_WEB_RESOURCE_UPDATE);
@@ -819,7 +854,7 @@ void PresetUpdater::priv::sync_update_flutter_resource(bool isAuto_check)
BOOST_LOG_TRIVIAL(fatal) << "request server flutter update data error:" << errorMsg; BOOST_LOG_TRIVIAL(fatal) << "request server flutter update data error:" << errorMsg;
} }
}) })
.perform_sync(); .perform();
} }
// Orca: sync config update for currect App version // Orca: sync config update for currect App version
void PresetUpdater::priv::sync_config(bool isAuto_check) void PresetUpdater::priv::sync_config(bool isAuto_check)
@@ -912,8 +947,18 @@ void PresetUpdater::priv::sync_config(bool isAuto_check)
return; return;
} }
if (currentPresetVersion < remoteVersion) if (currentPresetVersion < remoteVersion) {
download_file(fileUrl, fileName); if (fs::exists(fileName))
fs::remove(fileName);
fs::path tmpPath = fileName;
auto dirPath = tmpPath.parent_path() / "profiles/profiles";
if (fs::exists(dirPath))
fs::remove_all(dirPath);
download_file(fileUrl, fileName, "../ota/profiles/profiles/");
}
else { else {
if (!isAuto_check) { if (!isAuto_check) {
wxCommandEvent* evt = new wxCommandEvent(EVT_NO_PRESET_UPDATE); wxCommandEvent* evt = new wxCommandEvent(EVT_NO_PRESET_UPDATE);
@@ -928,7 +973,7 @@ void PresetUpdater::priv::sync_config(bool isAuto_check)
BOOST_LOG_TRIVIAL(fatal) << "request server preset update data error:" << errorMsg; BOOST_LOG_TRIVIAL(fatal) << "request server preset update data error:" << errorMsg;
} }
}) })
.perform_sync(); .perform();
} }
void PresetUpdater::priv::sync_tooltip(std::string http_url, std::string language) void PresetUpdater::priv::sync_tooltip(std::string http_url, std::string language)
@@ -1375,7 +1420,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
Updates updates; Updates updates;
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking for cached configuration updates..."; BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking for cached configuration updates...";
auto cache_profile_path = cache_path / "profiles"; auto cache_profile_path = cache_path / "profiles/profiles";
if (!fs::exists(cache_profile_path)) if (!fs::exists(cache_profile_path))
return updates; return updates;
@@ -1465,15 +1510,8 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
//BBS: switch to new BBL.json configs //BBS: switch to new BBL.json configs
bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
{ {
//std::string vendor_path;
//std::string vendor_name;
if (updates.incompats.size() > 0) { if (updates.incompats.size() > 0) {
//if (snapshot) {
// BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
// if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
// _u8L("Continue and install configuration updates?")))
// return false;
//}
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Deleting %1% incompatible bundles", updates.incompats.size()); BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Deleting %1% incompatible bundles", updates.incompats.size());
for (auto &incompat : updates.incompats) { for (auto &incompat : updates.incompats) {
@@ -1481,12 +1519,6 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
incompat.remove(); incompat.remove();
} }
} else if (updates.updates.size() > 0) { } else if (updates.updates.size() > 0) {
//if (snapshot) {
// BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
// if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
// _u8L("Continue and install configuration updates?")))
// return false;
//}
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Performing %1% updates", updates.updates.size()); BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Performing %1% updates", updates.updates.size());
@@ -1495,28 +1527,8 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
if (update.can_install) if (update.can_install)
update.install(); update.install();
//if (!update.is_directory) {
// vendor_path = update.source.parent_path().string();
// vendor_name = update.vendor;
//}
} }
//if (!vendor_path.empty()) {
// PresetBundle bundle;
// // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
// bundle.load_vendor_configs_from_json(vendor_path, vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
// BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
// auto preset_remover = [](const Preset& preset) {
// BOOST_LOG_TRIVIAL(info) << '\t' << preset.file;
// fs::remove(preset.file);
// };
// for (const auto &preset : bundle.prints) { preset_remover(preset); }
// for (const auto &preset : bundle.filaments) { preset_remover(preset); }
// for (const auto &preset : bundle.printers) { preset_remover(preset); }
//}
} }
return true; return true;
@@ -1555,12 +1567,9 @@ PresetUpdater::~PresetUpdater()
//BBS: refine the preset updater logic //BBS: refine the preset updater logic
void PresetUpdater::sync(std::string http_url, std::string language, std::string plugin_version, PresetBundle *preset_bundle) void PresetUpdater::sync(std::string http_url, std::string language, std::string plugin_version, PresetBundle *preset_bundle)
{ {
//p->set_download_prefs(GUI::wxGetApp().app_config);
if (!p->enabled_version_check && !p->enabled_config_update) { return; } if (!p->enabled_version_check && !p->enabled_config_update) { return; }
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
VendorMap vendors = preset_bundle ? preset_bundle->vendors : VendorMap{}; VendorMap vendors = preset_bundle ? preset_bundle->vendors : VendorMap{};
p->thread = std::thread([this, vendors, http_url, language, plugin_version]() { p->thread = std::thread([this, vendors, http_url, language, plugin_version]() {
@@ -1582,10 +1591,7 @@ void PresetUpdater::sync(std::string http_url, std::string language, std::string
return; return;
this->p->sync_plugins(http_url, plugin_version); this->p->sync_plugins(http_url, plugin_version);
this->p->sync_printer_config(http_url); this->p->sync_printer_config(http_url);
//if (p->cancel)
// return;
//remove the tooltip currently
//this->p->sync_tooltip(http_url, language);
}); });
} }
@@ -1603,9 +1609,7 @@ static bool reload_configs_update_gui()
// Reload global configuration // Reload global configuration
auto* app_config = GUI::wxGetApp().app_config; auto* app_config = GUI::wxGetApp().app_config;
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
// However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem); GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
GUI::wxGetApp().load_current_presets(); GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape(); GUI::wxGetApp().plater()->set_bed_shape();