Introducing Orca Cloud: https://cloud.orcaslicer.com (#13414)
* Add OrcaCloud sync platform and preset bundle sharing system Introduce OrcaCloud, a cloud sync platform for user presets, alongside a preset bundle system that enables sharing printer/filament/process profiles as local exportable bundles or subscribed cloud bundles. OrcaCloud platform: - Auth to Orca Cloud - Encrypted token storage (file-based or system keychain) - User preset sync with - Profile migration from default/bambu folders on first login - Homepage integration with entrance to cloud.orcaslicer.com Preset bundles: - Local bundle import/export with bundle_structure.json metadata - Subscribed cloud bundles with version-based update checking - Thread-safe concurrent bundle access with read-write mutex - Canonical bundle preset naming (_local/<id>/... and _subscribed/<id>/...) - Bundle presets are read-only; grouped under subheaders in combo boxes - PresetBundleDialog with auto-sync toggle, refresh, update notifications - Hyperlinked bundle names to cloud bundle pages Co-authored-by: Sabriel Koh <sabrielkcr@gmail.com> Co-authored-by: Derrick <derrick992110@gmail.com> Co-authored-by: Mykola Nahirnyi <mnahirnyi@amcbridge.com> Co-authored-by: Ian Chua <iancrb00@gmail.com> Co-authored-by: Draginraptor <draginraptor@gmail.com> Co-authored-by: ExPikaPaka <112851715+ExPikaPaka@users.noreply.github.com> Co-authored-by: Ian Bassi <ian.bassi@outlook.com> Co-authored-by: Ocraftyone <Ocraftyone@users.noreply.github.com> Co-authored-by: yw4z <ywsyildiz@gmail.com> Co-authored-by: peterm-m <101202951+peterm-m@users.noreply.github.com> * Fixed an issue on Windows it failed to login Orca Cloud with Google account
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@ Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignConsecutiveDeclarations: true
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
|
||||
@@ -1,23 +1,48 @@
|
||||
# Repository Guidelines
|
||||
# CLAUDE.md
|
||||
|
||||
## Project Structure & Module Organization
|
||||
OrcaSlicer’s C++17 sources live in `src/`, split by feature modules and platform adapters. User assets, icons, and printer presets are in `resources/`; translations stay in `localization/`. Tests sit in `tests/`, grouped by domain (`libslic3r/`, `sla_print/`, etc.) with fixtures under `tests/data/`. CMake helpers reside in `cmake/`, and longer references in `doc/` and `SoftFever_doc/`. Automation scripts belong in `scripts/` and `tools/`. Treat everything in `deps/` and `deps_src/` as vendored snapshots—do not modify without mirroring upstream tags.
|
||||
OrcaSlicer — open-source C++17 3D slicer. wxWidgets GUI, CMake build system.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
Use out-of-source builds:
|
||||
- `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release` configures dependencies and generates build files.
|
||||
- `cmake --build build --target OrcaSlicer --config Release` compiles the app; add `--parallel` to speed up.
|
||||
- `cmake --build build --target tests` then `ctest --test-dir build --output-on-failure` runs automated suites.
|
||||
Platform helpers such as `build_linux.sh`, `build_release_macos.sh`, and `build_release_vs2022.bat` wrap the same flow with toolchain flags. Use `build_release_macos.sh -sx` when reproducing macOS build issues, and `scripts/DockerBuild.sh` for reproducible container builds.
|
||||
## Build Commands
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
`.clang-format` enforces 4-space indents, a 140-column limit, aligned initializers, and brace wrapping for classes and functions. Run `clang-format -i <file>` before committing; the CMake `clang-format` target is available when LLVM tools are on your PATH. Prefer `CamelCase` for classes, `snake_case` for functions and locals, and `SCREAMING_CASE` for constants, matching conventions in `src/`. Keep headers self-contained and align include order with the IWYU pragmas.
|
||||
```bash
|
||||
# macOS
|
||||
cmake --build build/arm64 --config RelWithDebInfo --target all --
|
||||
|
||||
## Testing Guidelines
|
||||
Unit tests rely on Catch2 (`tests/catch2/`). Name specs after the component under test—for example `tests/libslic3r/TestPlanarHole.cpp`—and tag long-running cases so `ctest -L fast` remains useful. Cover new algorithms with deterministic fixtures or sample G-code stored in `tests/data/`. Document manual printer validation or regression slicer checks in your PR when automated coverage is insufficient.
|
||||
# Linux
|
||||
cmake --build build --config RelWithDebInfo --target all --
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
The history favors concise, sentence-style subject lines with optional issue references, e.g., `Fix grid lines origin for multiple plates (#10724)`. Squash fixups locally before opening a PR. Complete `.github/pull_request_template.md`, include reproduction steps or screenshots for UI changes, and mention impacted presets or translations. Link issues via `Closes #NNNN` when applicable, and call out dependency bumps or profile migrations for maintainer review.
|
||||
# Windows (replace %build_type% with Debug/Release/RelWithDebInfo)
|
||||
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||
```
|
||||
|
||||
## Security & Configuration Tips
|
||||
Follow `SECURITY.md` for vulnerability reporting. Keep API tokens and printer credentials out of tracked configs; use `sandboxes/` for experimental settings. When touching third-party code in `deps_src/`, record the upstream commit or release in your PR description and run the relevant platform build script to confirm integration.
|
||||
## Testing
|
||||
|
||||
Catch2 framework. Tests in `tests/` directory.
|
||||
|
||||
```bash
|
||||
cd build && ctest --output-on-failure # all tests
|
||||
ctest --test-dir ./tests/libslic3r # individual suite
|
||||
ctest --test-dir ./tests/fff_print
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
|
||||
- `#pragma once` for headers. Smart pointers and RAII preferred
|
||||
- Parallelization via TBB — be mindful of shared state
|
||||
|
||||
## Key Entry Points
|
||||
|
||||
- App startup: `src/OrcaSlicer.cpp`
|
||||
- Slicing pipeline: `src/libslic3r/Print.cpp`
|
||||
- All print/printer/material settings: `src/libslic3r/PrintConfig.cpp`
|
||||
- GUI: `src/slic3r/GUI/`
|
||||
- Core algorithms: `src/libslic3r/` (GCode/, Fill/, Support/, Geometry/, Format/, Arachne/)
|
||||
- Printer profiles: `resources/profiles/[manufacturer].json`
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
- **Backward compatibility required** for .3mf project files and printer profiles
|
||||
- **Cross-platform** — all changes must work on Windows, macOS, and Linux
|
||||
- Profile/format changes need version migration handling
|
||||
- Dependencies built separately in `deps/build/`, then linked to main app
|
||||
|
||||
@@ -1,234 +1 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
OrcaSlicer is an open-source 3D slicer application forked from Bambu Studio, built using C++ with wxWidgets for the GUI and CMake as the build system. The project uses a modular architecture with separate libraries for core slicing functionality, GUI components, and platform-specific code.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Building on Windows
|
||||
**Always use this command to build the project when testing build issues on Windows.**
|
||||
```bash
|
||||
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||
```
|
||||
|
||||
### Building on macOS
|
||||
**Always use this command to build the project when testing build issues on macOS.**
|
||||
```bash
|
||||
cmake --build build/arm64 --config RelWithDebInfo --target all --
|
||||
```
|
||||
|
||||
### Building on Linux
|
||||
**Always use this command to build the project when testing build issues on Linux.**
|
||||
```bash
|
||||
cmake --build build/arm64 --config RelWithDebInfo --target all --
|
||||
|
||||
```
|
||||
### Build test:
|
||||
|
||||
**Always use this command to build the project when testing build issues on Windows.**
|
||||
```bash
|
||||
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||
```
|
||||
|
||||
### Building on macOS
|
||||
**Always use this command to build the project when testing build issues on macOS.**
|
||||
```bash
|
||||
cmake --build build/arm64 --config RelWithDebInfo --target all --
|
||||
```
|
||||
|
||||
### Building on Linux
|
||||
**Always use this command to build the project when testing build issues on Linux.**
|
||||
```bash
|
||||
cmake --build build --config RelWithDebInfo --target all --
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Build System
|
||||
- Uses CMake with minimum version 3.13 (maximum 3.31.x on Windows)
|
||||
- Primary build directory: `build/`
|
||||
- Dependencies are built in `deps/build/`
|
||||
- The build process is split into dependency building and main application building
|
||||
- Windows builds use Visual Studio generators
|
||||
- macOS builds use Xcode by default, Ninja with -x flag
|
||||
- Linux builds use Ninja generator
|
||||
|
||||
### Testing
|
||||
Tests are located in the `tests/` directory and use the Catch2 testing framework. Test structure:
|
||||
- `tests/libslic3r/` - Core library tests (21 test files)
|
||||
- Geometry processing, algorithms, file formats (STL, 3MF, AMF)
|
||||
- Polygon operations, clipper utilities, Voronoi diagrams
|
||||
- `tests/fff_print/` - Fused Filament Fabrication tests (12 test files)
|
||||
- Slicing algorithms, G-code generation, print mechanics
|
||||
- Fill patterns, extrusion, support material
|
||||
- `tests/sla_print/` - Stereolithography tests (4 test files)
|
||||
- SLA-specific printing algorithms, support generation
|
||||
- `tests/libnest2d/` - 2D nesting algorithm tests
|
||||
- `tests/slic3rutils/` - Utility function tests
|
||||
- `tests/sandboxes/` - Experimental/sandbox test code
|
||||
|
||||
Run all tests after building:
|
||||
```bash
|
||||
cd build && ctest
|
||||
```
|
||||
|
||||
Run tests with verbose output:
|
||||
```bash
|
||||
cd build && ctest --output-on-failure
|
||||
```
|
||||
|
||||
Run individual test suites:
|
||||
```bash
|
||||
# From build directory
|
||||
ctest --test-dir ./tests/libslic3r/libslic3r_tests
|
||||
ctest --test-dir ./tests/fff_print/fff_print_tests
|
||||
ctest --test-dir ./tests/sla_print/sla_print_tests
|
||||
# and so on
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Libraries
|
||||
- **libslic3r/**: Core slicing engine and algorithms (platform-independent)
|
||||
- Main slicing logic, geometry processing, G-code generation
|
||||
- Key classes: Print, PrintObject, Layer, GCode, Config
|
||||
- Modular design with specialized subdirectories:
|
||||
- `GCode/` - G-code generation, cooling, pressure equalization, thumbnails
|
||||
- `Fill/` - Infill pattern implementations (gyroid, honeycomb, lightning, etc.)
|
||||
- `Support/` - Tree supports and traditional support generation
|
||||
- `Geometry/` - Advanced geometry operations, Voronoi diagrams, medial axis
|
||||
- `Format/` - File I/O for 3MF, AMF, STL, OBJ, STEP formats
|
||||
- `SLA/` - SLA-specific print processing and support generation
|
||||
- `Arachne/` - Advanced wall generation using skeletal trapezoidation
|
||||
|
||||
- **src/slic3r/**: Main application framework and GUI
|
||||
- GUI application built with wxWidgets
|
||||
- Integration between libslic3r core and user interface
|
||||
- Located in `src/slic3r/GUI/` (not shown in this directory but exists)
|
||||
|
||||
### Key Algorithmic Components
|
||||
- **Arachne Wall Generation**: Variable-width perimeter generation using skeletal trapezoidation
|
||||
- **Tree Supports**: Organic support generation algorithm
|
||||
- **Lightning Infill**: Sparse infill optimization for internal structures
|
||||
- **Adaptive Slicing**: Variable layer height based on geometry
|
||||
- **Multi-material**: Multi-extruder and soluble support processing
|
||||
- **G-code Post-processing**: Cooling, fan control, pressure advance, conflict checking
|
||||
|
||||
### File Format Support
|
||||
- **3MF/BBS_3MF**: Native format with extensions for multi-material and metadata
|
||||
- **STL**: Standard tessellation language for 3D models
|
||||
- **AMF**: Additive Manufacturing Format with color/material support
|
||||
- **OBJ**: Wavefront OBJ with material definitions
|
||||
- **STEP**: CAD format support for precise geometry
|
||||
- **G-code**: Output format with extensive post-processing capabilities
|
||||
|
||||
### External Dependencies
|
||||
- **Clipper2**: Advanced 2D polygon clipping and offsetting
|
||||
- **libigl**: Computational geometry library for mesh operations
|
||||
- **TBB**: Intel Threading Building Blocks for parallelization
|
||||
- **wxWidgets**: Cross-platform GUI framework
|
||||
- **OpenGL**: 3D graphics rendering and visualization
|
||||
- **CGAL**: Computational Geometry Algorithms Library (selective use)
|
||||
- **OpenVDB**: Volumetric data structures for advanced operations
|
||||
- **Eigen**: Linear algebra library for mathematical operations
|
||||
|
||||
## File Organization
|
||||
|
||||
### Resources and Configuration
|
||||
- `resources/profiles/` - Printer and material profiles organized by manufacturer
|
||||
- `resources/printers/` - Printer-specific configurations and G-code templates
|
||||
- `resources/images/` - UI icons, logos, calibration images
|
||||
- `resources/calib/` - Calibration test patterns and data
|
||||
- `resources/handy_models/` - Built-in test models (benchy, calibration cubes)
|
||||
|
||||
### Internationalization and Localization
|
||||
- `localization/i18n/` - Source translation files (.pot, .po)
|
||||
- `resources/i18n/` - Runtime language resources
|
||||
- Translation managed via `scripts/run_gettext.sh` / `scripts/run_gettext.bat`
|
||||
|
||||
### Platform-Specific Code
|
||||
- `src/libslic3r/Platform.cpp` - Platform abstractions and utilities
|
||||
- `src/libslic3r/MacUtils.mm` - macOS-specific utilities (Objective-C++)
|
||||
- Windows-specific build scripts and configurations
|
||||
- Linux distribution support scripts in `scripts/linux.d/`
|
||||
|
||||
### Build and Development Tools
|
||||
- `cmake/modules/` - Custom CMake find modules and utilities
|
||||
- `scripts/` - Python utilities for profile generation and validation
|
||||
- `tools/` - Windows build tools (gettext utilities)
|
||||
- `deps/` - External dependency build configurations
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Code Style and Standards
|
||||
- **C++17 standard** with selective C++20 features
|
||||
- **Naming conventions**: PascalCase for classes, snake_case for functions/variables
|
||||
- **Header guards**: Use `#pragma once`
|
||||
- **Memory management**: Prefer smart pointers, RAII patterns
|
||||
- **Thread safety**: Use TBB for parallelization, be mindful of shared state
|
||||
|
||||
### Common Development Tasks
|
||||
|
||||
#### Adding New Print Settings
|
||||
1. Define setting in `PrintConfig.cpp` with proper bounds and defaults
|
||||
2. Add UI controls in appropriate GUI components
|
||||
3. Update serialization in config save/load
|
||||
4. Add tooltips and help text for user guidance
|
||||
5. Test with different printer profiles
|
||||
|
||||
#### Modifying Slicing Algorithms
|
||||
1. Core algorithms live in `libslic3r/` subdirectories
|
||||
2. Performance-critical code should be profiled and optimized
|
||||
3. Consider multi-threading implications (TBB integration)
|
||||
4. Validate changes don't break existing profiles
|
||||
5. Add regression tests where appropriate
|
||||
|
||||
#### GUI Development
|
||||
1. GUI code resides in `src/slic3r/GUI/` (not visible in current tree)
|
||||
2. Use existing wxWidgets patterns and custom controls
|
||||
3. Support both light and dark themes
|
||||
4. Consider DPI scaling on high-resolution displays
|
||||
5. Maintain cross-platform compatibility
|
||||
|
||||
#### Adding Printer Support
|
||||
1. Create JSON profile in `resources/profiles/[manufacturer].json`
|
||||
2. Add printer-specific start/end G-code templates
|
||||
3. Configure build volume, capabilities, and material compatibility
|
||||
4. Test thoroughly with actual hardware when possible
|
||||
5. Follow existing profile structure and naming conventions
|
||||
|
||||
### Dependencies and Build System
|
||||
- **CMake-based** with separate dependency building phase
|
||||
- **Dependencies** built once in `deps/build/`, then linked to main application
|
||||
- **Cross-platform** considerations important for all changes
|
||||
- **Resource files** embedded at build time, platform-specific handling
|
||||
|
||||
### Performance Considerations
|
||||
- **Slicing algorithms** are CPU-intensive, profile before optimizing
|
||||
- **Memory usage** can be substantial with complex models
|
||||
- **Multi-threading** extensively used via TBB
|
||||
- **File I/O** optimized for large 3MF files with embedded textures
|
||||
- **Real-time preview** requires efficient mesh processing
|
||||
|
||||
## Important Development Notes
|
||||
|
||||
### Codebase Navigation
|
||||
- Use search tools extensively - codebase has 500k+ lines
|
||||
- Key entry points: `src/OrcaSlicer.cpp` for application startup
|
||||
- Core slicing: `libslic3r/Print.cpp` orchestrates the slicing pipeline
|
||||
- Configuration: `PrintConfig.cpp` defines all print/printer/material settings
|
||||
|
||||
### Compatibility and Stability
|
||||
- **Backward compatibility** maintained for project files and profiles
|
||||
- **Cross-platform** support essential (Windows/macOS/Linux)
|
||||
- **File format** changes require careful version handling
|
||||
- **Profile migrations** needed when settings change significantly
|
||||
|
||||
### Quality and Testing
|
||||
- **Regression testing** important due to algorithm complexity
|
||||
- **Performance benchmarks** help catch performance regressions
|
||||
- **Memory leak** detection important for long-running GUI application
|
||||
- **Cross-platform** testing required before releases
|
||||
@AGENTS.md
|
||||
+10
-1
@@ -504,6 +504,15 @@ if [[ -n "${USE_LLD}" ]] ; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Auto-detect ccache for faster rebuilds
|
||||
export CMAKE_CCACHE_ARGS=()
|
||||
if command -v ccache >/dev/null 2>&1 ; then
|
||||
echo "ccache found at $(command -v ccache), enabling compiler caching..."
|
||||
export CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
|
||||
else
|
||||
echo "Note: ccache not found. Install ccache for faster rebuilds."
|
||||
fi
|
||||
|
||||
if [[ -n "${BUILD_DEPS}" ]] ; then
|
||||
echo "Configuring dependencies..."
|
||||
read -r -a BUILD_ARGS <<< "${DEPS_EXTRA_BUILD_ARGS}"
|
||||
@@ -536,7 +545,7 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
|
||||
BUILD_ARGS+=(-DORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY}")
|
||||
fi
|
||||
|
||||
print_and_run cmake -S . -B $BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G "Ninja Multi-Config" \
|
||||
print_and_run cmake -S . -B $BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G "Ninja Multi-Config" \
|
||||
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
|
||||
-DORCA_TOOLS=ON \
|
||||
"${COLORED_OUTPUT}" \
|
||||
|
||||
@@ -111,6 +111,7 @@ var LangText = {
|
||||
orca3: "Stealth Mode",
|
||||
orca4: "This stops the transmission of data to Bambu's cloud services. Users who don't use BBL machines or use LAN mode only can safely turn on this function.",
|
||||
orca5: "Enable Stealth Mode.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
ca_ES: {
|
||||
t1: "Benvingut a Orca Slicer",
|
||||
@@ -220,6 +221,7 @@ var LangText = {
|
||||
t113: "Pots canviar la teva elecció en les preferències en qualsevol moment.",
|
||||
orca1: "Editar Informació del Projecte",
|
||||
orca2: "no hi ha informació del model",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
es_ES: {
|
||||
t1: "Bienvenido a Orca Slicer",
|
||||
@@ -333,6 +335,7 @@ var LangText = {
|
||||
orca3: "Modo sigiloso",
|
||||
orca4: "Esta función detiene la transmisión de datos a los servicios en la nube de Bambu. Los usuarios que no utilicen máquinas BBL o que solo usen el modo LAN pueden activar esta función con seguridad.",
|
||||
orca5: "Activar modo sigiloso.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
it_IT: {
|
||||
t1: "Benvenuti in OrcaSlicer",
|
||||
@@ -445,6 +448,7 @@ var LangText = {
|
||||
orca3: "Modalità invisibile",
|
||||
orca4: "Con questa modalità, la trasmissione dei dati ai servizi cloud di Bambu sarà interrotta. Gli utenti che non utilizzano macchine BBL o che usano solo la modalità LAN possono attivare questa funzione in modo sicuro.",
|
||||
orca5: "Abilita la modalità invisibile.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
de_DE: {
|
||||
t1: "Willkommen im Orca Slicer",
|
||||
@@ -548,6 +552,7 @@ var LangText = {
|
||||
t126: "Laden……",
|
||||
orca1: "Edit Project Info",
|
||||
orca2: "no model information",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
cs_CZ: {
|
||||
t1: "Vítejte v Orca Slicer",
|
||||
@@ -651,6 +656,7 @@ var LangText = {
|
||||
t126: "Načtení probíhá……",
|
||||
orca1: "Edit Project Info",
|
||||
orca2: "no model information",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
fr_FR: {
|
||||
t1: "Bienvenue sur Orca Slicer",
|
||||
@@ -773,6 +779,7 @@ var LangText = {
|
||||
wk14: "Par rapport au format STL, le format STEP apporte des informations plus efficaces. Grâce à la grande précision de ce format, de nombreuses trajectoires d'extrusion peuvent être générées sous forme d'arcs. Il inclut également la relation d'assemblage de chaque pièce d'un modèle, qui peut être utilisée pour restaurer la vue d'assemblage après la coupe d'un modèle.",
|
||||
wk15: "Texte 3D",
|
||||
wk16: "Avec l'outil Texte 3D, les utilisateurs peuvent facilement créer diverses formes de texte 3D dans le projet, ce qui rend le modèle plus personnalisé. Orca Slicer fournit des dizaines de polices et prend en charge les styles gras et italique pour donner au texte une plus grande flexibilité.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
zh_CN: {
|
||||
t1: "欢迎使用Orca Slicer",
|
||||
@@ -899,6 +906,7 @@ var LangText = {
|
||||
wk16: "使用3D文本工具,用户可以轻松地在项目中创建各种3D文本形状,使模型更加个性化。Orca Slicer提供了数十种字体,并支持粗体和斜体样式,使文本具有更大的灵活性。",
|
||||
orca1: "编辑项目信息",
|
||||
orca2: "该模型没有相关信息",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
zh_TW: {
|
||||
t1: "歡迎使用 Orca Slicer",
|
||||
@@ -1004,6 +1012,7 @@ var LangText = {
|
||||
wk16: "使用3D文字工具,使用者可以輕鬆地在項目中建立各種3D文字形狀,使模型更加個性化。Orca Slicer 提供了數十種字體,並支援粗體和斜體樣式,使文字具有更大的靈活性。",
|
||||
orca1: "編輯專案資訊",
|
||||
orca2: "沒有模型相關資訊",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
ru_RU: {
|
||||
t1: "Приветствуем в Orca Slicer!",
|
||||
@@ -1116,7 +1125,8 @@ var LangText = {
|
||||
orca2: "Информация отсутствует",
|
||||
orca3: "Режим конфиденциальности",
|
||||
orca4: "Это остановит передачу данных в облачные сервисы Bambu. Помешает только владельцам Bambu Lab, не использующим режим «Только LAN».",
|
||||
orca5: "Включить режим конфиденциальности"
|
||||
orca5: "Включить режим конфиденциальности",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
ko_KR: {
|
||||
t1: "Orca Slicer에 오신 것을 환영합니다",
|
||||
@@ -1209,6 +1219,7 @@ var LangText = {
|
||||
t126: "로딩 중……",
|
||||
orca1: "Edit Project Info",
|
||||
orca2: "no model information",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
tr_TR: {
|
||||
t1: "Orca Slicer'a hoş geldiniz",
|
||||
@@ -1322,6 +1333,7 @@ var LangText = {
|
||||
orca3: "Gizli Mod",
|
||||
orca4: "Bu, Bambu'nun bulut hizmetlerine veri iletimini durdurur. BBL makinelerini kullanmayan veya yalnızca LAN modunu kullanan kullanıcılar bu işlevi güvenle açabilir.",
|
||||
orca5: "Gizli Modu etkinleştirin.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
pl_PL: {
|
||||
t1: "Witamy w Orca Slicer",
|
||||
@@ -1435,6 +1447,7 @@ var LangText = {
|
||||
orca3: "Tryb «Niewidzialny»",
|
||||
orca4: "To wyłączy przesyłanie danych do usług chmurowych Bambu. Użytkownicy, którzy nie korzystają z maszyn BBL lub używają tylko trybu LAN, mogą bez obaw włączyć tę opcję.",
|
||||
orca5: "Włącz tryb «Niewidzialny»",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
pt_BR: {
|
||||
t1: "Bem-vindo ao Orca Slicer",
|
||||
@@ -1548,6 +1561,7 @@ var LangText = {
|
||||
orca3: "Modo Furtivo",
|
||||
orca4: "Isso interrompe a transmissão de dados para os serviços de nuvem da Bambu. Usuários que não usam máquinas BBL ou usam somente o modo LAN podem ativar essa função com segurança.",
|
||||
orca5: "Habilita Modo Furtivo.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
lt_LT: {
|
||||
t1: "Pasisveikinkite su Orca Slicer",
|
||||
@@ -1660,6 +1674,7 @@ var LangText = {
|
||||
orca3: "Slaptas režimas",
|
||||
orca4: "Tai sustabdo duomenų perdavimą į Bambu debesijos paslaugas. Vartotojai, kurie nenaudoja BBL mašinų arba naudoja tik LAN režimą, gali drąsiai įjungti šią funkciją.",
|
||||
orca5: "Įjungti slaptą režimą.",
|
||||
orca6: "Bambu Cloud",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Cache-Control" content="max-age=0" />
|
||||
<title>Export Presets</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
|
||||
<link rel="stylesheet" type="text/css" href="../css/common.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
|
||||
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
|
||||
<script type="text/javascript" src="../js/json2.js"></script>
|
||||
<script type="text/javascript" src="../../data/text.js"></script>
|
||||
<script type="text/javascript" src="../js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="../js/common.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body onLoad="OnInit()">
|
||||
|
||||
<!-- ORCA column browser -->
|
||||
<div class="cbr-browser-container">
|
||||
<div class="cbr-column">
|
||||
<div class="cbr-column-title-container">
|
||||
<div class="search-icon"></div>
|
||||
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="1"/>
|
||||
<span class="cbr-search-placeholder trans" tid="t15">printer</span>
|
||||
<div class="clear-icon"></div>
|
||||
</div>
|
||||
<div class="cbr-content thin-scroll" id="MachineList">
|
||||
<div class="CValues">
|
||||
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span class="trans" tid="t11">all</span></label>
|
||||
</div>
|
||||
<div class="cbr-no-items">No items</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbr-column">
|
||||
<div class="cbr-column-title-container">
|
||||
<div class="search-icon"></div>
|
||||
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="2"/>
|
||||
<span class="cbr-search-placeholder trans" tid="t16">filament</span>
|
||||
<div class="clear-icon"></div>
|
||||
</div>
|
||||
<div class="cbr-content thin-scroll" id="FilatypeList">
|
||||
<div class="CValues">
|
||||
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllFilament()" /><span class="trans" tid="t11">all</span></label>
|
||||
</div>
|
||||
<div class="cbr-no-items">No items</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbr-column">
|
||||
<div class="cbr-column-title-container">
|
||||
<div class="search-icon"></div>
|
||||
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="3"/>
|
||||
<span class="cbr-search-placeholder trans" tid="t17">presets</span>
|
||||
<div class="clear-icon"></div>
|
||||
</div>
|
||||
<div class="cbr-content thin-scroll" id="PresetList">
|
||||
<div class="CValues">
|
||||
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllPreset()" /><span class="trans" tid="t11">all</span></label>
|
||||
</div>
|
||||
<div class="cbr-no-items">No items</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="AcceptArea">
|
||||
<div id="export_cloud_btn" class="ButtonStyleConfirm ButtonTypeChoice">Export to OrcaCloud</div>
|
||||
<div id="export_local_btn" class="ButtonStyleRegular ButtonTypeChoice">Export to folder</div>
|
||||
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,374 @@
|
||||
var g_profile = {
|
||||
machines: [],
|
||||
filaments: [],
|
||||
presets: []
|
||||
};
|
||||
|
||||
var g_search = {
|
||||
machine: "",
|
||||
filament: "",
|
||||
preset: ""
|
||||
};
|
||||
|
||||
function OnInit()
|
||||
{
|
||||
if (typeof TranslatePage === "function")
|
||||
TranslatePage();
|
||||
|
||||
InstallInputSafeKeydown();
|
||||
BindSearchInputs();
|
||||
BindClearIcons();
|
||||
BindBottomButtons();
|
||||
|
||||
// Always load demo data first so the page works without C++ backend.
|
||||
// LoadDemoProfile();
|
||||
RequestProfile();
|
||||
}
|
||||
|
||||
function InstallInputSafeKeydown()
|
||||
{
|
||||
// common.js blocks all key events globally; allow typing in text inputs.
|
||||
document.onkeydown = function (event) {
|
||||
var e = event || window.event || arguments.callee.caller.arguments[0];
|
||||
var target = e && e.target ? e.target : null;
|
||||
var tag = target && target.tagName ? String(target.tagName).toUpperCase() : "";
|
||||
var type = target && target.type ? String(target.type).toLowerCase() : "";
|
||||
|
||||
var editable =
|
||||
!!(target && target.isContentEditable) ||
|
||||
tag === "TEXTAREA" ||
|
||||
(tag === "INPUT" && type !== "checkbox" && type !== "radio" && type !== "button" && type !== "submit");
|
||||
|
||||
if (editable)
|
||||
return true;
|
||||
|
||||
if (e && e.keyCode === 27 && typeof ClosePage === "function")
|
||||
ClosePage();
|
||||
|
||||
if (window.event) {
|
||||
try { e.keyCode = 0; } catch (err) { }
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
if (e && typeof e.preventDefault === "function")
|
||||
e.preventDefault();
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function RequestProfile()
|
||||
{
|
||||
SendMessage("request_export_preset_profile", {});
|
||||
}
|
||||
|
||||
function HandleStudio(pVal)
|
||||
{
|
||||
var payload = (typeof pVal === "string") ? SafeJsonParse(pVal) : pVal;
|
||||
if (!payload || typeof payload !== "object")
|
||||
return;
|
||||
|
||||
var cmd = String(payload.command || "");
|
||||
if (cmd === "response_export_preset_profile" ) {
|
||||
ApplyProfile(payload.data);
|
||||
}
|
||||
}
|
||||
|
||||
function ApplyProfile(profile)
|
||||
{
|
||||
|
||||
g_profile.machines = BuildNameRows(profile.printers);
|
||||
g_profile.filaments = BuildNameRows(profile.filaments);
|
||||
g_profile.presets = BuildNameRows(profile.process);
|
||||
|
||||
RenderColumn("MachineList", g_profile.machines, "mode", "MachineClick");
|
||||
RenderColumn("FilatypeList", g_profile.filaments, "filatype", "FilaClick");
|
||||
RenderColumn("PresetList", g_profile.presets, "preset", "PresetClick");
|
||||
|
||||
ApplyColumnSearch("MachineList", g_search.machine);
|
||||
ApplyColumnSearch("FilatypeList", g_search.filament);
|
||||
ApplyColumnSearch("PresetList", g_search.preset);
|
||||
}
|
||||
|
||||
function BuildNameRows(names)
|
||||
{
|
||||
var src = Array.isArray(names) ? names : [];
|
||||
var out = [];
|
||||
|
||||
for (var n = 0; n < src.length; n++) {
|
||||
var row = src[n];
|
||||
if (row === undefined || row === null)
|
||||
continue;
|
||||
|
||||
var name = String(row);
|
||||
out.push({ id: name, label: name, checked: false });
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function RenderColumn(listId, items, attrName, onChangeFn)
|
||||
{
|
||||
var root = $("#" + listId + " .CValues");
|
||||
if (!root.length)
|
||||
return;
|
||||
|
||||
root.find("label:gt(0)").remove();
|
||||
|
||||
var html = "";
|
||||
for (var n = 0; n < items.length; n++) {
|
||||
var one = items[n];
|
||||
html += '<label data-dynamic="1">' +
|
||||
'<input type="checkbox" data-key="' + EscapeAttr(one.id) + '" ' + attrName + '="' + EscapeAttr(one.id) + '"' +
|
||||
(one.checked ? ' checked="checked"' : "") +
|
||||
' onChange="' + onChangeFn + '()" />' +
|
||||
'<span title="' + EscapeAttr(one.label) + '">' + EscapeHtml(one.label) + '</span>' +
|
||||
'</label>';
|
||||
}
|
||||
|
||||
root.append(html);
|
||||
SyncMasterCheckbox(listId);
|
||||
ToggleNoItems(listId, items.length === 0);
|
||||
}
|
||||
|
||||
function ChooseAllMachine()
|
||||
{
|
||||
var checked = !!$("#MachineList .CValues input:first").prop("checked");
|
||||
$("#MachineList .CValues input:gt(0)").prop("checked", checked);
|
||||
SyncListFromDom("MachineList", g_profile.machines);
|
||||
}
|
||||
|
||||
function MachineClick()
|
||||
{
|
||||
SyncMasterCheckbox("MachineList");
|
||||
SyncListFromDom("MachineList", g_profile.machines);
|
||||
}
|
||||
|
||||
function ChooseAllFilament()
|
||||
{
|
||||
var checked = !!$("#FilatypeList .CValues input:first").prop("checked");
|
||||
$("#FilatypeList .CValues input:gt(0)").prop("checked", checked);
|
||||
SyncListFromDom("FilatypeList", g_profile.filaments);
|
||||
}
|
||||
|
||||
function FilaClick()
|
||||
{
|
||||
SyncMasterCheckbox("FilatypeList");
|
||||
SyncListFromDom("FilatypeList", g_profile.filaments);
|
||||
}
|
||||
|
||||
function ChooseAllPreset()
|
||||
{
|
||||
var checked = !!$("#PresetList .CValues input:first").prop("checked");
|
||||
$("#PresetList .CValues input:gt(0)").prop("checked", checked);
|
||||
SyncListFromDom("PresetList", g_profile.presets);
|
||||
}
|
||||
|
||||
function PresetClick()
|
||||
{
|
||||
SyncMasterCheckbox("PresetList");
|
||||
SyncListFromDom("PresetList", g_profile.presets);
|
||||
}
|
||||
|
||||
function SyncMasterCheckbox(listId)
|
||||
{
|
||||
var all = $("#" + listId + " .CValues input:gt(0)");
|
||||
var master = $("#" + listId + " .CValues input:first");
|
||||
|
||||
if (!all.length) {
|
||||
master.prop("checked", false);
|
||||
return;
|
||||
}
|
||||
|
||||
master.prop("checked", all.length === all.filter(":checked").length);
|
||||
}
|
||||
|
||||
function SyncListFromDom(listId, store)
|
||||
{
|
||||
var map = {};
|
||||
for (var n = 0; n < store.length; n++)
|
||||
map[store[n].id] = store[n];
|
||||
|
||||
$("#" + listId + " .CValues input:gt(0)").each(function () {
|
||||
var id = String($(this).attr("data-key") || "");
|
||||
if (map[id])
|
||||
map[id].checked = !!$(this).prop("checked");
|
||||
});
|
||||
}
|
||||
|
||||
function BindSearchInputs()
|
||||
{
|
||||
var inputs = document.querySelectorAll(".cbr-search-bar");
|
||||
|
||||
if (inputs.length > 0) {
|
||||
inputs[0].addEventListener("input", function () {
|
||||
g_search.machine = String(this.value || "").toLowerCase();
|
||||
ApplyColumnSearch("MachineList", g_search.machine);
|
||||
});
|
||||
}
|
||||
|
||||
if (inputs.length > 1) {
|
||||
inputs[1].addEventListener("input", function () {
|
||||
g_search.filament = String(this.value || "").toLowerCase();
|
||||
ApplyColumnSearch("FilatypeList", g_search.filament);
|
||||
});
|
||||
}
|
||||
|
||||
if (inputs.length > 2) {
|
||||
inputs[2].addEventListener("input", function () {
|
||||
g_search.preset = String(this.value || "").toLowerCase();
|
||||
ApplyColumnSearch("PresetList", g_search.preset);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ApplyColumnSearch(listId, query)
|
||||
{
|
||||
var rows = $("#" + listId + " .CValues label:gt(0)");
|
||||
var visibleCount = 0;
|
||||
|
||||
rows.each(function () {
|
||||
var row = $(this);
|
||||
var text = String(row.text() || "").toLowerCase();
|
||||
var key = String(row.find("input").attr("data-key") || "").toLowerCase();
|
||||
|
||||
if (!query || text.indexOf(query) >= 0 || key.indexOf(query) >= 0) {
|
||||
row.show();
|
||||
visibleCount++;
|
||||
}
|
||||
else {
|
||||
row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
ToggleNoItems(listId, visibleCount === 0);
|
||||
}
|
||||
|
||||
function ToggleNoItems(listId, show)
|
||||
{
|
||||
var node = $("#" + listId + " .cbr-no-items");
|
||||
if (!node.length)
|
||||
return;
|
||||
|
||||
if (show)
|
||||
node.addClass("show");
|
||||
else
|
||||
node.removeClass("show");
|
||||
}
|
||||
|
||||
function BindClearIcons()
|
||||
{
|
||||
var icons = document.querySelectorAll(".clear-icon");
|
||||
|
||||
for (var n = 0; n < icons.length; n++) {
|
||||
icons[n].addEventListener("click", function () {
|
||||
var parent = this.parentElement;
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
var input = parent.querySelector("input[type='text']");
|
||||
if (!input)
|
||||
return;
|
||||
|
||||
input.value = "";
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
input.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function BindBottomButtons()
|
||||
{
|
||||
var backBtn = document.getElementById("back_btn");
|
||||
var exportCloud = document.getElementById("export_cloud_btn")
|
||||
var exportLocal = document.getElementById("export_local_btn");
|
||||
var closeBtn = document.getElementById("close_btn");
|
||||
|
||||
backBtn?.addEventListener("click", function () {
|
||||
SendMessage("navigate_back", {});
|
||||
});
|
||||
|
||||
|
||||
exportLocal?.addEventListener("click", function () {
|
||||
SendMessage("export_local", BuildResultPayload());
|
||||
});
|
||||
|
||||
|
||||
closeBtn?.addEventListener("click", () => {
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "close_page"
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
});
|
||||
}
|
||||
|
||||
function BuildResultPayload()
|
||||
{
|
||||
return {
|
||||
machines: g_profile.machines.filter(function (x) { return x.checked; }).map(function (x) { return x.id; }),
|
||||
filaments: g_profile.filaments.filter(function (x) { return x.checked; }).map(function (x) { return x.id; }),
|
||||
presets: g_profile.presets.filter(function (x) { return x.checked; }).map(function (x) { return x.id; })
|
||||
};
|
||||
}
|
||||
|
||||
function LoadDemoProfile()
|
||||
{
|
||||
ApplyProfile({
|
||||
machines: [
|
||||
{ id: "printer_x1c_04", name: "X1 Carbon 0.4 nozzle", selected: 1 },
|
||||
{ id: "printer_p1s_04", name: "P1S 0.4 nozzle", selected: 1 },
|
||||
{ id: "printer_a1_04", name: "A1 0.4 nozzle", selected: 0 },
|
||||
{ id: "printer_prusa_mk4_04", name: "Prusa MK4 0.4 nozzle", selected: 1 }
|
||||
],
|
||||
filaments: [
|
||||
{ id: "filament_generic_pla", name: "Generic PLA", selected: 1 },
|
||||
{ id: "filament_generic_petg", name: "Generic PETG", selected: 1 },
|
||||
{ id: "filament_bambu_abs", name: "Bambu ABS", selected: 0 },
|
||||
{ id: "filament_esun_pla_plus", name: "eSUN PLA+", selected: 1 }
|
||||
],
|
||||
presets: [
|
||||
{ id: "preset_quality_020", name: "Quality 0.20mm", selected: 1 },
|
||||
{ id: "preset_quality_012", name: "Quality 0.12mm", selected: 0 },
|
||||
{ id: "preset_speed_024", name: "Speed 0.24mm", selected: 1 },
|
||||
{ id: "preset_draft_028", name: "Draft 0.28mm", selected: 0 }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function SendMessage(command, data)
|
||||
{
|
||||
var msg = {};
|
||||
msg.sequence_id = Math.round(new Date() / 1000);
|
||||
msg.command = command;
|
||||
if (data && typeof data === "object")
|
||||
msg.data = data;
|
||||
|
||||
if (typeof SendWXMessage === "function")
|
||||
SendWXMessage(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
function SafeJsonParse(str)
|
||||
{
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
}
|
||||
catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function EscapeHtml(str)
|
||||
{
|
||||
return String(str)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function EscapeAttr(str)
|
||||
{
|
||||
return EscapeHtml(str);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
:root {
|
||||
--cbr-border-color: #d2d2d7;
|
||||
--cbr-header-bg: #f6f7f9;
|
||||
--cbr-panel-bg: #ffffff;
|
||||
--cbr-input-bg: #ffffff;
|
||||
--cbr-input-focus-bg: #f2f8f7;
|
||||
--cbr-label-color: #7b7b84;
|
||||
--cbr-icon-color: #75757f;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--cbr-border-color: #4a4a51;
|
||||
--cbr-header-bg: #2f2f34;
|
||||
--cbr-panel-bg: #2d2d31;
|
||||
--cbr-input-bg: #2d2d31;
|
||||
--cbr-input-focus-bg: #3b3b41;
|
||||
--cbr-label-color: #b9b9bc;
|
||||
--cbr-icon-color: #b9b9bc;
|
||||
}
|
||||
}
|
||||
|
||||
.cbr-browser-container {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
margin: 10px 15px 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
border: 1px solid var(--cbr-border-color);
|
||||
background: var(--cbr-panel-bg);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cbr-column {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cbr-column:not(:last-child) {
|
||||
border-right: 1px solid var(--cbr-border-color);
|
||||
}
|
||||
|
||||
.cbr-column-title-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
padding: 3px 6px;
|
||||
background: var(--cbr-header-bg);
|
||||
border-bottom: 1px solid var(--cbr-border-color);
|
||||
}
|
||||
|
||||
.cbr-search-bar {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
padding: 2px 26px 2px 26px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background: var(--cbr-input-bg);
|
||||
}
|
||||
|
||||
.cbr-search-bar:hover,
|
||||
.cbr-search-bar:focus {
|
||||
border-color: var(--main-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.cbr-search-bar:focus {
|
||||
background: var(--cbr-input-focus-bg);
|
||||
}
|
||||
|
||||
.cbr-search-placeholder {
|
||||
position: absolute;
|
||||
left: 33px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--cbr-label-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cbr-search-bar:not(:placeholder-shown) + .cbr-search-placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.search-icon,
|
||||
.clear-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--cbr-icon-color);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
left: 11px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.search-icon::before {
|
||||
content: "";
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
box-sizing: border-box;
|
||||
border: 1.8px solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.search-icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 1.8px;
|
||||
background: currentColor;
|
||||
transform: rotate(45deg);
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
right: 11px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.clear-icon::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 1.8px;
|
||||
background: currentColor;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.clear-icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 1.8px;
|
||||
background: currentColor;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.cbr-search-bar:not(:placeholder-shown) ~ .clear-icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cbr-content {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.cbr-column .CValues {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.CValues label {
|
||||
margin: 0 !important;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.cbr-content .cbr-no-items {
|
||||
display: none;
|
||||
color: var(--cbr-label-color);
|
||||
font-size: 12px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.cbr-content .cbr-no-items.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#AcceptArea {
|
||||
border-top: 1px solid var(--cbr-border-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Preset Bundle</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
|
||||
<link rel="stylesheet" type="text/css" href="../css/common.css" />
|
||||
<!-- <link rel="stylesheet" type="text/css" href="23.css" /> -->
|
||||
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
|
||||
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
|
||||
<script type="text/javascript" src="../js/json2.js"></script>
|
||||
<script type="text/javascript" src="../../data/text.js"></script>
|
||||
<script type="text/javascript" src="../js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="../js/common.js"></script>
|
||||
<!-- <script type="text/javascript" src="./23.js"></script> -->
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body onLoad="OnInit()">
|
||||
<div class="app">
|
||||
<div class="split">
|
||||
<div class="top-toolbar">
|
||||
<button id="refresh_btn" class="ButtonStyleConfirm ButtonTypeChoice toolbar-btn">Refresh</button>
|
||||
|
||||
<!-- <label class="auto-update-switch" for="auto_update_toggle">
|
||||
<span class="auto-update-label">Auto update</span>
|
||||
<input id="auto_update_toggle" type="checkbox" />
|
||||
<span class="auto-update-slider" aria-hidden="true">
|
||||
<span class="auto-update-text auto-update-text-off">OFF</span>
|
||||
<span class="auto-update-text auto-update-text-on">ON</span>
|
||||
</span>
|
||||
</label> -->
|
||||
</div>
|
||||
|
||||
<section class="pane top">
|
||||
<div class="hdr top-cols">
|
||||
<span>Name</span>
|
||||
<span>Type</span>
|
||||
<span>Version</span>
|
||||
<span>Update</span>
|
||||
</div>
|
||||
<div id="topList" class="body"></div>
|
||||
</section>
|
||||
|
||||
<section class="pane bottom">
|
||||
<div class="hdr bot-cols">
|
||||
<span>Name</span>
|
||||
<span>Type</span>
|
||||
</div>
|
||||
<div id="bottomList" class="body"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div id="AcceptArea">
|
||||
<div id="export_btn" class="ButtonStyleConfirm ButtonTypeChoice" hidden>Export Presets</div>
|
||||
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
|
||||
</div>
|
||||
|
||||
<!-- Context Menu -->
|
||||
<div id="ctxMenu" class="ctx" hidden>
|
||||
<button class="ctx-item" data-action="open_folder">Open folder in explorer</button>
|
||||
<button id = "delete_btn" class="ctx-item-delete" data-action="delete_bundle" hidden>Delete bundle</button>
|
||||
<button id = "unsubscribe_btn" class="ctx-item-subscribed" data-action="unsubscribe_bundle" hidden>Unsubscribe bundle</button>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,375 @@
|
||||
// ========= Data Stores =========
|
||||
const bundlesById = new Map(); // bundleId -> bundle object
|
||||
const printersByBundle = new Map(); // bundleId -> Map(index -> printerName)
|
||||
const filamentsByBundle = new Map(); // bundleId -> Map(index -> filamentName)
|
||||
const presetsByBundle = new Map(); // bundleId -> Map(index -> presetName)
|
||||
const UPDATE_TOOLTIP = "Update available";
|
||||
const UNAUTHORIZED_TOOLTIP = "Unauthorized bundle";
|
||||
|
||||
// ========= DOM =========
|
||||
let topList = null;
|
||||
let bottomList = null;
|
||||
|
||||
let ctxMenu = null;
|
||||
let contextRow = null;
|
||||
let ctxMenuSubscribed = null;
|
||||
|
||||
let ctxMenuDelete = null;
|
||||
|
||||
let selectedBundleId = null;
|
||||
|
||||
// ========= Init =========
|
||||
function OnInit() {
|
||||
|
||||
topList = document.getElementById("topList");
|
||||
bottomList = document.getElementById("bottomList");
|
||||
ctxMenu = document.getElementById("ctxMenu");
|
||||
ctxMenuSubscribed = document.getElementById("unsubscribe_btn");
|
||||
ctxMenuDelete = document.getElementById("delete_btn");
|
||||
const closeBtn = document.getElementById("close_btn");
|
||||
const exportbtn = document.getElementById("export_btn");
|
||||
const refreshBtn = document.getElementById("refresh_btn");
|
||||
const autoUpdateToggle = document.getElementById("auto_update_toggle");
|
||||
|
||||
if (!topList || !bottomList) return;
|
||||
TranslatePage();
|
||||
|
||||
// If wx side needs to request bundles after page load:
|
||||
RequestBundles();
|
||||
|
||||
|
||||
refreshBtn?.addEventListener("click", () => {
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "refresh_bundles"
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
});
|
||||
|
||||
autoUpdateToggle?.addEventListener("change", () => {
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "set_auto_update",
|
||||
enabled: !!autoUpdateToggle.checked
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
});
|
||||
// Hook selection on top list
|
||||
topList.addEventListener("click", (e) => {
|
||||
const cloudLink = e.target.closest(".bundle-cloud-link");
|
||||
if (cloudLink) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const row = cloudLink.closest(".row");
|
||||
if (!row) return;
|
||||
|
||||
selectTopRow(row);
|
||||
selectedBundleId = String(row.dataset.id || "");
|
||||
renderBottomForBundle(selectedBundleId);
|
||||
sendOpenBundleOnCloud(selectedBundleId);
|
||||
return;
|
||||
}
|
||||
|
||||
const updateBtn = e.target.closest(".bundle-update-btn");
|
||||
if (updateBtn) {
|
||||
e.stopPropagation();
|
||||
if (updateBtn.disabled) return;
|
||||
|
||||
const row = updateBtn.closest(".row");
|
||||
if (!row) return;
|
||||
|
||||
selectTopRow(row);
|
||||
selectedBundleId = String(row.dataset.id || "");
|
||||
renderBottomForBundle(selectedBundleId);
|
||||
sendUpdateBundleCommand(selectedBundleId);
|
||||
return;
|
||||
}
|
||||
|
||||
const row = e.target.closest(".row");
|
||||
if (!row) return;
|
||||
|
||||
selectTopRow(row);
|
||||
selectedBundleId = String(row.dataset.id || "");
|
||||
renderBottomForBundle(selectedBundleId);
|
||||
});
|
||||
|
||||
// for top list rows if right click open context menu
|
||||
topList.addEventListener("contextmenu", (e) => {
|
||||
const row = e.target.closest(".row");
|
||||
if (!row) return; // top rows only
|
||||
|
||||
const bundleType = String(row.dataset.bundleType || "").toLowerCase();
|
||||
if (bundleType !== "subscribed") return;
|
||||
|
||||
e.preventDefault();
|
||||
selectTopRow(row);
|
||||
contextRow = row;
|
||||
showSubscribedMenu(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
// for top list rows except subscribed if right click open regular context menu
|
||||
topList.addEventListener("contextmenu", (e) => {
|
||||
const row = e.target.closest(".row");
|
||||
if (!row) return; // top rows only
|
||||
const bundleType = String(row.dataset.bundleType || "").toLowerCase();
|
||||
if (bundleType === "subscribed") return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
selectTopRow(row);
|
||||
contextRow = row;
|
||||
showMenu(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
ctxMenu?.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("[data-action]");
|
||||
if (!btn || !contextRow) return;
|
||||
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "top_row_menu_action",
|
||||
action: String(btn.dataset.action || ""),
|
||||
bundle_id: String(contextRow.dataset.id || "")
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
hideMenu();
|
||||
});
|
||||
|
||||
closeBtn?.addEventListener("click", () => {
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "close_page"
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
});
|
||||
|
||||
exportbtn?.addEventListener("click", () => {
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "export_page"
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!e.target.closest(".ctx")) hideMenu();
|
||||
});
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") hideMenu();
|
||||
});
|
||||
}
|
||||
// ========= wx bridge requests =========
|
||||
|
||||
function RequestBundles() {
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="request_bundles";
|
||||
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
}
|
||||
|
||||
function HandleStudio(pVal) {
|
||||
|
||||
const msg = (typeof pVal === "string") ? safeJsonParse(pVal) : pVal;
|
||||
if (!msg || typeof msg !== "object") return;
|
||||
|
||||
const strCmd = msg.command;
|
||||
if (strCmd === "list_bundles") {
|
||||
unpackPayload(msg);
|
||||
renderTop();
|
||||
// auto-select first bundle if none selected
|
||||
autoSelectFirstBundle();
|
||||
|
||||
const autoUpdateToggle = document.getElementById("auto_update_toggle");
|
||||
if (autoUpdateToggle) {
|
||||
autoUpdateToggle.checked = !!msg.auto_update_enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========= Parse / store =========
|
||||
function unpackPayload(payload) {
|
||||
bundlesById.clear();
|
||||
printersByBundle.clear();
|
||||
filamentsByBundle.clear();
|
||||
presetsByBundle.clear();
|
||||
|
||||
const list = payload?.data || [];
|
||||
for (const bundle of list) {
|
||||
const id = String(bundle.id ?? "");
|
||||
if (!id) continue;
|
||||
|
||||
bundlesById.set(id, {
|
||||
id,
|
||||
name: bundle.name ?? "",
|
||||
type: bundle.type ?? "",
|
||||
version: bundle.version ?? "",
|
||||
path: bundle.path ?? "",
|
||||
update_available: Boolean(bundle.update_available) ,
|
||||
unauthorized: Boolean(bundle.unauthorized)
|
||||
});
|
||||
|
||||
printersByBundle.set(id, new Map((bundle.printers || []).map((name, i) => [i, name])));
|
||||
filamentsByBundle.set(id, new Map((bundle.filaments || []).map((name, i) => [i, name])));
|
||||
presetsByBundle.set(id, new Map((bundle.presets || []).map((name, i) => [i, name])));
|
||||
}
|
||||
}
|
||||
|
||||
// ========= Render: top =========
|
||||
function renderTop() {
|
||||
const bundles = Array.from(bundlesById.values());
|
||||
|
||||
topList.innerHTML = bundles.map(b => `
|
||||
<div class="row" data-id="${escapeAttr(b.id)}" data-bundle-type="${escapeAttr(String(b.type || "").toLowerCase())}">
|
||||
<div class="cell bundle-name-cell" title="${escapeAttr(b.name)}">
|
||||
${b.unauthorized
|
||||
? `<span class="bundle-status-icon bundle-status-icon-unauthorized" title="${escapeAttr(UNAUTHORIZED_TOOLTIP)}" aria-label="${escapeAttr(UNAUTHORIZED_TOOLTIP)}">!</span>`
|
||||
: b.update_available
|
||||
? `<span class="bundle-status-icon bundle-status-icon-update" title="${escapeAttr(UPDATE_TOOLTIP)}" aria-label="${escapeAttr(UPDATE_TOOLTIP)}">↑</span>`
|
||||
: `<span class="bundle-status-icon-spacer" aria-hidden="true"></span>`}
|
||||
${
|
||||
b.type === "Subscribed" ?
|
||||
`<a href="#" class="bundle-name-text bundle-cloud-link" title="Open this bundle in your browser">${escapeHtml(b.name)}</a>`
|
||||
: `<span class="bundle-name-text">${escapeHtml(b.name)}</span>`
|
||||
}
|
||||
</div>
|
||||
<span title="${escapeAttr(b.type)}">${escapeHtml(b.type)}</span>
|
||||
<span title="${escapeAttr(b.version)}">${escapeHtml(b.version)}</span>
|
||||
<div class="cell bundle-update-cell">
|
||||
<button
|
||||
type="button"
|
||||
class="bundle-update-btn ${(!b.unauthorized && b.update_available) ? "is-enabled" : "is-disabled"}"
|
||||
${(!b.unauthorized && b.update_available) ? "" : "disabled"}
|
||||
data-id="${escapeAttr(b.id)}"
|
||||
>Update</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function sendOpenBundleOnCloud(bundleId) {
|
||||
const bundle = bundlesById.get(String(bundleId || ""));
|
||||
if (!bundle) return;
|
||||
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "open_bundle_on_cloud",
|
||||
bundle_id: String(bundle.id || "")
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
}
|
||||
|
||||
function sendUpdateBundleCommand(bundleId) {
|
||||
const bundle = bundlesById.get(String(bundleId || ""));
|
||||
if (!bundle || bundle.unauthorized || !bundle.update_available) return;
|
||||
|
||||
const tSend = {
|
||||
sequence_id: Math.round(Date.now() / 1000),
|
||||
command: "update_bundle",
|
||||
bundle_id: String(bundle.id || "")
|
||||
};
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
}
|
||||
|
||||
// ========= Render: bottom (for a selected bundle) =========
|
||||
function renderBottomForBundle(bundleId) {
|
||||
const key = String(bundleId || "");
|
||||
const printers = printersByBundle.get(key) || new Map();
|
||||
const filaments = filamentsByBundle.get(key) || new Map();
|
||||
const presets = presetsByBundle.get(key) || new Map();
|
||||
|
||||
// Convert to a flat list of rows { typeLabel, name }
|
||||
const rows = [];
|
||||
|
||||
for (const [, name] of printers) rows.push({ type: "Printer", name });
|
||||
for (const [, name] of filaments) rows.push({ type: "Filament", name });
|
||||
for (const [, name] of presets) rows.push({ type: "Preset", name });
|
||||
|
||||
bottomList.innerHTML = rows.map((r, idx) => `
|
||||
<div class="row" data-id="${escapeAttr(bundleId)}" data-idx="${idx}">
|
||||
<span>${escapeHtml(r.name)}</span>
|
||||
<span title="${escapeAttr(r.type)}">${escapeHtml(r.type)}</span>
|
||||
</div>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
// ========= Selection helpers =========
|
||||
function clearSelection() {
|
||||
document.querySelectorAll(".row.selected").forEach(r => r.classList.remove("selected"));
|
||||
}
|
||||
|
||||
function selectTopRow(rowEl) {
|
||||
// only clear selection in top list, not bottom
|
||||
topList.querySelectorAll(".row.selected").forEach(r => r.classList.remove("selected"));
|
||||
rowEl.classList.add("selected");
|
||||
}
|
||||
|
||||
function autoSelectFirstBundle() {
|
||||
if (selectedBundleId && bundlesById.has(selectedBundleId)) {
|
||||
// reselect existing
|
||||
const el = topList.querySelector(`.row[data-id="${cssEscape(selectedBundleId)}"]`);
|
||||
if (el) selectTopRow(el);
|
||||
renderBottomForBundle(selectedBundleId);
|
||||
return;
|
||||
}
|
||||
|
||||
const first = topList.querySelector(".row");
|
||||
if (!first) {
|
||||
bottomList.innerHTML = "";
|
||||
selectedBundleId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
selectTopRow(first);
|
||||
selectedBundleId = first.dataset.id;
|
||||
renderBottomForBundle(selectedBundleId);
|
||||
}
|
||||
|
||||
function showSubscribedMenu(x, y) {
|
||||
if (!ctxMenu) return;
|
||||
ctxMenu.style.left = `${x}px`;
|
||||
ctxMenu.style.top = `${y}px`;
|
||||
ctxMenu.hidden = false;
|
||||
ctxMenuDelete.hidden = true;
|
||||
ctxMenuSubscribed.hidden = false;
|
||||
}
|
||||
|
||||
function showMenu(x, y) {
|
||||
if (!ctxMenu) return;
|
||||
ctxMenu.style.left = `${x}px`;
|
||||
ctxMenu.style.top = `${y}px`;
|
||||
ctxMenu.hidden = false;
|
||||
ctxMenuDelete.hidden = false;
|
||||
ctxMenuSubscribed.hidden = true;
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
if (!ctxMenu) return;
|
||||
ctxMenu.hidden = true;
|
||||
ctxMenuSubscribed.hidden = true;
|
||||
contextRow = null;
|
||||
}
|
||||
// ========= Utilities =========
|
||||
function safeJsonParse(s) {
|
||||
try { return JSON.parse(s); } catch { return null; }
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function escapeAttr(str) {
|
||||
// minimal attribute escaping
|
||||
return escapeHtml(str);
|
||||
}
|
||||
|
||||
function cssEscape(str) {
|
||||
// basic css escape for attribute selectors
|
||||
return String(str).replaceAll('"', '\\"');
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--panel: #ffffff;
|
||||
--border: #d8d8d8;
|
||||
--border-strong: #e6e6e6;
|
||||
--border-soft: #f0f0f0;
|
||||
--col-sep: #e1e1e1;
|
||||
|
||||
--text: #1f2328;
|
||||
--row-hover: #f7f9fb;
|
||||
--row-selected: #eaf2ff;
|
||||
--row-selected-outline: #b7d0ff;
|
||||
|
||||
--footer-bg: #fafafa;
|
||||
--btn-bg: #ffffff;
|
||||
--btn-border: #cccccc;
|
||||
--btn-hover: #f0f0f0;
|
||||
|
||||
--ctx-bg: #ffffff;
|
||||
--ctx-border: #cccccc;
|
||||
--ctx-hover: #efefef;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* App layout: split content + footer */
|
||||
.app {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Split panes */
|
||||
.split {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1.25fr) minmax(140px, 0.75fr);
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.top-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
min-width: 92px;
|
||||
}
|
||||
|
||||
.auto-update-switch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.auto-update-switch input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.auto-update-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auto-update-slider {
|
||||
position: relative;
|
||||
width: 52px;
|
||||
height: 24px;
|
||||
flex: 0 0 52px;
|
||||
border-radius: 999px;
|
||||
background: #b9b9b9;
|
||||
transition: background 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auto-update-slider::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
|
||||
transition: transform 0.2s ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.auto-update-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.3px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.auto-update-text-off {
|
||||
right: 7px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.auto-update-text-on {
|
||||
left: 7px;
|
||||
color: #ffffff;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.auto-update-switch input:checked + .auto-update-slider {
|
||||
background: var(--main-color);
|
||||
}
|
||||
|
||||
.auto-update-switch input:checked + .auto-update-slider::before {
|
||||
transform: translateX(28px);
|
||||
}
|
||||
|
||||
.auto-update-switch input:checked + .auto-update-slider .auto-update-text-on {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.auto-update-switch input:checked + .auto-update-slider .auto-update-text-off {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.auto-update-switch input:not(:checked) + .auto-update-slider .auto-update-text-on {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.auto-update-switch input:not(:checked) + .auto-update-slider .auto-update-text-off {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.auto-update-switch input:focus-visible + .auto-update-slider {
|
||||
outline: 2px solid var(--main-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.auto-update-switch input:disabled + .auto-update-slider {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.pane {
|
||||
min-height: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.hdr {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid var(--border-strong);
|
||||
padding: 8px 10px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.top-cols { grid-template-columns: minmax(0, 2fr) 1fr 1fr 88px; }
|
||||
.bot-cols { grid-template-columns: 2fr 1fr; }
|
||||
|
||||
.top .row { grid-template-columns: minmax(0, 2fr) 1fr 1fr 88px; }
|
||||
.bottom .row { grid-template-columns: 2fr 1fr; }
|
||||
|
||||
.body {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hdr > *,
|
||||
.row > * {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 8px;
|
||||
border-right: 1px solid var(--col-sep);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hdr > *:last-child,
|
||||
.row > *:last-child {
|
||||
border-right: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.bundle-name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bundle-name-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bundle-cloud-link {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.bundle-cloud-link:hover,
|
||||
.bundle-cloud-link:focus-visible {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bundle-status-icon,
|
||||
.bundle-status-icon-spacer {
|
||||
flex: 0 0 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.bundle-status-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #f59e0b;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.bundle-status-icon-unauthorized {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.bundle-status-icon-update {
|
||||
background: var(--main-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bundle-update-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.bundle-update-btn {
|
||||
box-sizing: border-box;
|
||||
min-width: 64px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.bundle-update-btn.is-enabled {
|
||||
background: var(--main-color);
|
||||
color: var(--button-fg-light);
|
||||
}
|
||||
|
||||
.bundle-update-btn.is-enabled:hover {
|
||||
background: var(--main-color-hover);
|
||||
}
|
||||
|
||||
.bundle-update-btn.is-disabled {
|
||||
background: var(--button-bg-disabled);
|
||||
color: var(--button-fg-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
#topList,
|
||||
#bottomList {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Column separators + clipping */
|
||||
|
||||
.row {
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.row:hover { background: var(--row-hover); }
|
||||
|
||||
.row.selected {
|
||||
background: var(--row-selected);
|
||||
outline: 1px solid var(--row-selected-outline);
|
||||
}
|
||||
|
||||
/* Footer styling
|
||||
.footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 16px;
|
||||
background: var(--footer-bg);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--btn-border);
|
||||
background: var(--btn-bg);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.footer-btn:hover { background: var(--btn-hover); } */
|
||||
|
||||
/* Context menu */
|
||||
.ctx {
|
||||
position: fixed;
|
||||
min-width: 180px;
|
||||
background: var(--ctx-bg);
|
||||
border: 1px solid var(--ctx-border);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,.18);
|
||||
padding: 4px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.ctx-item,
|
||||
.ctx-item-subscribed, .ctx-item-delete {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ctx-item:hover,
|
||||
.ctx-item-subscribed:hover ,
|
||||
.ctx-item-delete:hover
|
||||
{ background: var(--ctx-hover); }
|
||||
|
||||
.ctx-item[hidden],
|
||||
.ctx-item-subscribed[hidden] ,
|
||||
.ctx-item-delete[hidden]{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#AcceptArea {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
*
|
||||
{
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
font-family: "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-sans;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html
|
||||
{
|
||||
height:100%;
|
||||
background-color: #626262;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
height:100%;
|
||||
max-height: 660px;
|
||||
max-width: 820px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.TextPoint
|
||||
{
|
||||
font-size:1px;
|
||||
}
|
||||
|
||||
.ZScrol::-webkit-scrollbar {/*滚动条整体样式*/
|
||||
width: 12px; /*高宽分别对应横竖滚动条的尺寸*/
|
||||
height: 12px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
|
||||
border-radius: 6px;
|
||||
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
|
||||
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
|
||||
background-color: #AAAAAA;
|
||||
}
|
||||
|
||||
.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/
|
||||
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
|
||||
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
|
||||
border-radius: 10px;
|
||||
background: #EDEDED;
|
||||
}
|
||||
|
||||
/*----Three Part----*/
|
||||
body
|
||||
{
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
#Title
|
||||
{
|
||||
height: 12%;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
flex-direction:column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#Title div
|
||||
{
|
||||
font-size:28px;
|
||||
line-height: 28px;
|
||||
color: #009688;
|
||||
padding: 0px 10mm;
|
||||
}
|
||||
|
||||
|
||||
#Content
|
||||
{
|
||||
height: 76%;
|
||||
padding: 20px 40px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #464646;
|
||||
position: relative;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#Content div
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
#AcceptArea
|
||||
{
|
||||
height:var(--dialog-button-sizer-height); /*----ORCA Use fixed size to prevent position change----*/
|
||||
max-height:var(--dialog-button-sizer-height);
|
||||
min-height:var(--dialog-button-sizer-height);
|
||||
padding: 0 var(--dialog-button-gap);
|
||||
text-align: left;
|
||||
display: flex;
|
||||
justify-content:flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/*---HyperLink---*/
|
||||
.HyperLink
|
||||
{
|
||||
color: #009688;
|
||||
text-decoration: underline;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*---Checkboxes ORCA ---*/
|
||||
label:has(input[type="checkbox"]){
|
||||
margin:0;
|
||||
padding: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
label:has(input[type="checkbox"])>span{
|
||||
vertical-align: middle;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color:#FFFFFF;
|
||||
margin:0;
|
||||
margin-right: 6px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border: 0.1em solid #DBDBDB;
|
||||
border-radius: 0.15em;
|
||||
display: inline-flex;
|
||||
place-content: center;
|
||||
background-color:#FFFFFF;
|
||||
}
|
||||
|
||||
input[type="checkbox"]::before {
|
||||
content: "";
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
transform: scale(0);
|
||||
box-shadow: inset 1em 1em #FFFFFF;
|
||||
clip-path: polygon(7% 37%, 0 45%, 33% 78%, 100% 30%, 95% 21%, 33% 64%);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
border-color:#009688;
|
||||
background-color:#009688;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/*----------------Light Mode-------------------*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
:root {
|
||||
--bg: #1b1f24;
|
||||
--panel: #242a31;
|
||||
--border: #3a424d;
|
||||
--border-strong: #3a424d;
|
||||
--border-soft: #313843;
|
||||
--col-sep: #3a424d;
|
||||
|
||||
--text: #e6ebf0;
|
||||
--row-hover: #2b3340;
|
||||
--row-selected: #244945;
|
||||
--row-selected-outline: #00bfa5;
|
||||
|
||||
--footer-bg: #20262d;
|
||||
--btn-bg: #2a313a;
|
||||
--btn-border: #4b5664;
|
||||
--btn-hover: #333c47;
|
||||
|
||||
--ctx-bg: #2a313a;
|
||||
--ctx-border: #4b5664;
|
||||
--ctx-hover: #3a4451;
|
||||
}
|
||||
|
||||
*
|
||||
{
|
||||
color: #efeff0;
|
||||
border-color: #B9B9BC;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
background-color:#2D2D31; /* ORCA match background color */
|
||||
color: #efeff0;
|
||||
}
|
||||
|
||||
.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
|
||||
background-color: #939594;
|
||||
}
|
||||
|
||||
.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/
|
||||
background: #161817;
|
||||
}
|
||||
|
||||
#Title div
|
||||
{
|
||||
color: #009688;
|
||||
}
|
||||
|
||||
.search>input[type=text]{
|
||||
background-color:#2D2D31;
|
||||
}
|
||||
|
||||
/*---Checkboxes ORCA---*/
|
||||
input[type=checkbox]{
|
||||
background-color:#2D2D31;
|
||||
border-color:#4A4A51;
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked{
|
||||
background-color:#009688;
|
||||
}
|
||||
|
||||
/*-------Text------*/
|
||||
|
||||
.TextS1
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.TextS2
|
||||
{
|
||||
color:#B9B9BC;
|
||||
}
|
||||
|
||||
/*---Policy---*/
|
||||
.TextArea1
|
||||
{
|
||||
background-color: #4A4A51;
|
||||
color: #BEBEC0;
|
||||
}
|
||||
|
||||
/*----Region---*/
|
||||
.RegionItem:hover
|
||||
{
|
||||
background-color:#4C4C55;
|
||||
}
|
||||
|
||||
.RegionSelected:hover
|
||||
{
|
||||
background-color:#009688;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/*----Menu----*/
|
||||
#Title div.TitleUnselected
|
||||
{
|
||||
color: #BEBEC0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#FullArea
|
||||
{
|
||||
height:100%;
|
||||
}
|
||||
|
||||
|
||||
#LoadProgress
|
||||
{
|
||||
position:fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 3mm;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#PercentTip
|
||||
{
|
||||
width:70%;
|
||||
height: 100%;
|
||||
background-color: #335DFC;
|
||||
}
|
||||
|
||||
|
||||
#PageArea
|
||||
{
|
||||
width:100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#IEPage
|
||||
{
|
||||
height:100%;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
*
|
||||
{
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
html,body
|
||||
{
|
||||
height:100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#PageArea
|
||||
{
|
||||
height:100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 35px;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
font-size: 18px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.swiper-slide iframe
|
||||
{
|
||||
width:100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.CBtn
|
||||
{
|
||||
padding: 3mm 100m;
|
||||
background-color: #77EFF9;
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
top:30px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
function ClosePage() {
|
||||
var tSend = {};
|
||||
tSend['sequence_id'] = Math.round(new Date() / 1000);
|
||||
tSend['command'] = "close_page";
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
}
|
||||
|
||||
document.onkeydown = function (event) {
|
||||
var e = event || window.event || arguments.callee.caller.arguments[0];
|
||||
|
||||
if (window.event) {
|
||||
try { e.keyCode = 0; } catch (e) { }
|
||||
e.returnValue = false;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('wheel', function (event) {
|
||||
if (event.ctrlKey === true || event.metaKey) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}, { passive: false });
|
||||
@@ -277,4 +277,63 @@ function SendWXMessage( strMsg )
|
||||
}
|
||||
}
|
||||
|
||||
/*------CSS Link Control----*/
|
||||
function RemoveCssLink( LinkPath )
|
||||
{
|
||||
let pNow=$("head link[href='"+LinkPath+"']");
|
||||
|
||||
let nTotal=pNow.length;
|
||||
for( let n=0;n<nTotal;n++ )
|
||||
{
|
||||
pNow[n].remove();
|
||||
}
|
||||
}
|
||||
|
||||
function AddCssLink( LinkPath )
|
||||
{
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var link = document.createElement('link');
|
||||
link.href = LinkPath;
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
head.appendChild(link);
|
||||
}
|
||||
|
||||
function CheckCssLinkExist( LinkPath )
|
||||
{
|
||||
let pNow=$("head link[href='"+LinkPath+"']");
|
||||
let nTotal=pNow.length;
|
||||
|
||||
return nTotal;
|
||||
}
|
||||
|
||||
|
||||
/*------Dark Mode------*/
|
||||
|
||||
function SwitchDarkMode( DarkCssPath )
|
||||
{
|
||||
ExecuteDarkMode( DarkCssPath );
|
||||
setInterval("ExecuteDarkMode('"+DarkCssPath+"')",1000);
|
||||
}
|
||||
|
||||
function ExecuteDarkMode( DarkCssPath )
|
||||
{
|
||||
let nMode=0;
|
||||
let bDarkMode=navigator.userAgent.match( RegExp('dark','i') );
|
||||
if( bDarkMode!=null )
|
||||
nMode=1;
|
||||
|
||||
let nNow=CheckCssLinkExist(DarkCssPath);
|
||||
if( nMode==0 )
|
||||
{
|
||||
if(nNow>0)
|
||||
RemoveCssLink(DarkCssPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(nNow==0)
|
||||
AddCssLink(DarkCssPath);
|
||||
}
|
||||
}
|
||||
|
||||
SwitchDarkMode( "../css/dark.css" );
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
|
||||
function NextSlide()
|
||||
{
|
||||
$('.swiper-button-next').click();
|
||||
}
|
||||
|
||||
function PreSlide()
|
||||
{
|
||||
$('.swiper-button-prev').click();
|
||||
}
|
||||
+4
File diff suppressed because one or more lines are too long
@@ -127,4 +127,9 @@ body
|
||||
{
|
||||
background: rgba(54, 54, 60, 0.88);
|
||||
border: 1px solid rgba(129, 129, 131, 0.64);
|
||||
}
|
||||
}
|
||||
|
||||
/*--- Bambu Cloud Section ---*/
|
||||
#BambuCloudHeader { color: #818183; }
|
||||
#BambuCloudHeader:hover { color: #efeff0; }
|
||||
.bambu-chevron svg path { stroke: currentColor; }
|
||||
@@ -79,40 +79,151 @@ body
|
||||
|
||||
#LoginArea
|
||||
{
|
||||
height: 180px;
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
width:262px;
|
||||
}
|
||||
|
||||
|
||||
#Login1
|
||||
#OrcaLoginSection
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#OrcaLogin1
|
||||
{
|
||||
height:36px;
|
||||
line-height: 36px;
|
||||
display: flex;
|
||||
flex-direction: column; /*ORCA*/
|
||||
align-items: center; /*Allow icon centered vertically*/
|
||||
justify-content: center; /*and use login button in new line*/
|
||||
user-select: none;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#OrcaLogin2
|
||||
{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 14px 0 16px;
|
||||
box-sizing: border-box;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
/* --- Bambu Cloud Section --- */
|
||||
#BambuCloudSection
|
||||
{
|
||||
display: none; /* shown by cloud_providers_info from backend */
|
||||
border-top: 1px solid;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
#BambuCloudHeader
|
||||
{
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #A8A8A8;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#BambuCloudHeader:hover
|
||||
{
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.bambu-chevron
|
||||
{
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.bambu-chevron.expanded
|
||||
{
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.bambu-status-dot
|
||||
{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #A8A8A8;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.bambu-status-dot.online
|
||||
{
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
#BambuCloudBody
|
||||
{
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.25s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#BambuCloudBody.expanded
|
||||
{
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
#NoPluginTip
|
||||
{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
position: static;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 5px;
|
||||
padding: 5px 10px;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
#BambuLogin1
|
||||
{
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#BambuLogin2
|
||||
{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
#BambuAvatarIcon
|
||||
{
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#BambuUserName
|
||||
{
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
@@ -132,18 +243,14 @@ body
|
||||
height:96px; /*ORCA use bigger icon to fit logo size*/
|
||||
}
|
||||
|
||||
#Login2
|
||||
{
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 262px;
|
||||
}
|
||||
|
||||
#UserAvatarIcon
|
||||
{
|
||||
height: 85px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: block;
|
||||
margin: 2px auto 10px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#UserName
|
||||
@@ -153,11 +260,13 @@ body
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
width: 80%;
|
||||
max-width: 190px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
#LogoutBtn
|
||||
{
|
||||
margin-top: 5px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/*------------------*/
|
||||
@@ -201,6 +310,38 @@ body
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.BtnShortcut
|
||||
{
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.ShortcutBrandIcon
|
||||
{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ShortcutTextRow
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ShortcutMark
|
||||
{
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-left: auto;
|
||||
color: currentColor;
|
||||
opacity: 0.72;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------*/
|
||||
#RightBoard
|
||||
@@ -321,6 +462,7 @@ body
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#RecentTitleBlock
|
||||
@@ -348,6 +490,7 @@ body
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.FileItem
|
||||
@@ -735,4 +878,4 @@ body
|
||||
opacity: 1!important;
|
||||
cursor: pointer!important;
|
||||
pointer-events: auto!important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,32 +20,72 @@
|
||||
<script type="text/javascript" src="js/home.js"></script>
|
||||
</head>
|
||||
<body class="ZScrol" onLoad="OnInit()">
|
||||
<div id="LeftBoard" style="display: none;">
|
||||
<div id="LeftBoard">
|
||||
<div id="LoginArea">
|
||||
<div id="Login1">
|
||||
<div id="Icon1"><img id="BBLIcon" src="../image/logo.png" /></div> <!-- ORCA use square icon for better consistency on UI -->
|
||||
<div id="LoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnLoginOrRegister()"><span class="trans" tid="t26">login</span> / <span class="trans" tid="t27">register</span></div>
|
||||
</div>
|
||||
|
||||
<div id="Login2">
|
||||
<div>
|
||||
<img id="UserAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
|
||||
|
||||
<!-- Orca Login Section -->
|
||||
<div id="OrcaLoginSection">
|
||||
<div id="OrcaLogin1">
|
||||
<div id="Icon1"><img id="BBLIcon" src="../image/logo.png" /></div> <!-- ORCA use square icon for better consistency on UI -->
|
||||
<div id="LoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnLoginOrRegister()"><span class="trans" tid="t26">login</span> / <span class="trans" tid="t27">register</span></div>
|
||||
</div>
|
||||
|
||||
<div id="OrcaLogin2">
|
||||
<div>
|
||||
<img id="UserAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
|
||||
</div>
|
||||
<div id="UserName" class="TextS1"></div>
|
||||
<div id="LogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnLogOut()">log out</div>
|
||||
</div>
|
||||
<div id="UserName" class="TextS1"></div>
|
||||
<div id="LogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnLogOut()">log out</div>
|
||||
</div>
|
||||
|
||||
<div id="NoPluginTip">
|
||||
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bambu Cloud Section -->
|
||||
<div id="BambuCloudSection">
|
||||
<div id="BambuCloudHeader" onClick="ToggleBambuSection()">
|
||||
<svg class="bambu-chevron" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 2.5L8 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span class="trans" tid="orca6">Bambu Cloud</span>
|
||||
<span class="bambu-status-dot"></span>
|
||||
</div>
|
||||
|
||||
<div id="BambuCloudBody">
|
||||
<div id="BambuLogin1">
|
||||
<div id="BambuLoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnBambuLoginOrRegister()"><span class="trans" tid="t26">login</span></div>
|
||||
</div>
|
||||
|
||||
<div id="BambuLogin2">
|
||||
<div>
|
||||
<img id="BambuAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
|
||||
</div>
|
||||
<div id="BambuUserName" class="TextS1"></div>
|
||||
<div id="BambuLogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnBambuLogOut()">log out</div>
|
||||
</div>
|
||||
|
||||
<div id="NoPluginTip">
|
||||
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="BtnArea">
|
||||
<div menu="recent" class="BtnItem BtnItemSelected" onClick="GotoMenu('recent')">
|
||||
<div class="BtnIcon "><img class="LeftIcon" src="img/i2.png" /></div>
|
||||
<div class="trans" tid="t28">recent</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="BtnItem BtnShortcut" onClick="OpenUrlInLocalBrowser('https://cloud.orcaslicer.com')">
|
||||
<div class="BtnIcon "><img class="ShortcutBrandIcon" src="../image/logo.png" /></div>
|
||||
<div class="ShortcutTextRow">
|
||||
<div>OrcaCloud</div>
|
||||
<svg class="ShortcutMark" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M6 3.5H3.5C2.95 3.5 2.5 3.95 2.5 4.5V12.5C2.5 13.05 2.95 13.5 3.5 13.5H11.5C12.05 13.5 12.5 13.05 12.5 12.5V10" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 3.5H13.5V8.5" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 3.5L7 10" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//var TestData={"sequence_id":"0","command":"get_recent_projects","response":[{"path":"D:\\work\\Models\\Toy\\3d-puzzle-cube-model_files\\3d-puzzle-cube.3mf","time":"2022\/3\/24 20:33:10"},{"path":"D:\\work\\Models\\Art\\Carved Stone Vase - remeshed+drainage\\Carved Stone Vase.3mf","time":"2022\/3\/24 17:11:51"},{"path":"D:\\work\\Models\\Art\\Kity & Cat\\Cat.3mf","time":"2022\/3\/24 17:07:55"},{"path":"D:\\work\\Models\\Toy\\鐩村墤.3mf","time":"2022\/3\/24 17:06:02"},{"path":"D:\\work\\Models\\Toy\\minimalistic-dual-tone-whistle-model_files\\minimalistic-dual-tone-whistle.3mf","time":"2022\/3\/22 21:12:22"},{"path":"D:\\work\\Models\\Toy\\spiral-city-model_files\\spiral-city.3mf","time":"2022\/3\/22 18:58:37"},{"path":"D:\\work\\Models\\Toy\\impossible-dovetail-puzzle-box-model_files\\impossible-dovetail-puzzle-box.3mf","time":"2022\/3\/22 20:08:40"}]};
|
||||
|
||||
var m_HotModelList=null;
|
||||
var bambuSectionExpanded = false;
|
||||
|
||||
function OnInit()
|
||||
{
|
||||
@@ -8,6 +9,7 @@ function OnInit()
|
||||
TranslatePage();
|
||||
|
||||
SendMsg_GetLoginInfo();
|
||||
SendMsg_GetBambuLoginInfo();
|
||||
SendMsg_GetRecentFile();
|
||||
SendMsg_GetStaffPick();
|
||||
}
|
||||
@@ -77,11 +79,7 @@ function Set_RecentFile_MouseRightBtn_Event()
|
||||
|
||||
function SetLoginPanelVisibility(visible) {
|
||||
var leftBoard = document.getElementById("LeftBoard");
|
||||
if (visible) {
|
||||
leftBoard.style.display = "block";
|
||||
} else {
|
||||
leftBoard.style.display = "none";
|
||||
}
|
||||
leftBoard.style.display = "block";
|
||||
}
|
||||
|
||||
function HandleStudio( pVal )
|
||||
@@ -90,24 +88,41 @@ function HandleStudio( pVal )
|
||||
|
||||
if (strCmd == "get_recent_projects") {
|
||||
ShowRecentFileList(pVal["response"]);
|
||||
} else if (strCmd == "studio_userlogin") {
|
||||
SetLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
|
||||
} else if (strCmd == "studio_useroffline") {
|
||||
SetUserOffline();
|
||||
} else if (strCmd == "orca_userlogin") {
|
||||
SetOrcaLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
|
||||
} else if (strCmd == "orca_useroffline") {
|
||||
SetOrcaUserOffline();
|
||||
} else if (strCmd == "studio_bambu_userlogin") {
|
||||
SetBambuLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
|
||||
} else if (strCmd == "studio_bambu_useroffline") {
|
||||
SetBambuUserOffline();
|
||||
} else if (strCmd == "studio_set_mallurl") {
|
||||
SetMallUrl(pVal["data"]["url"]);
|
||||
} else if (strCmd == "studio_clickmenu") {
|
||||
let strName = pVal["data"]["menu"];
|
||||
|
||||
GotoMenu(strName);
|
||||
} else if (strCmd == "cloud_providers_info") {
|
||||
if (pVal["data"]["providers"] && pVal["data"]["providers"].indexOf("bbl") >= 0) {
|
||||
$("#BambuCloudSection").show();
|
||||
} else {
|
||||
$("#BambuCloudSection").hide();
|
||||
}
|
||||
} else if (strCmd == "network_plugin_installtip") {
|
||||
let nShow = pVal["show"] * 1;
|
||||
|
||||
if (nShow == 1) {
|
||||
// Auto-expand Bambu section to show the tip
|
||||
if (!bambuSectionExpanded) ToggleBambuSection();
|
||||
$("#BambuLogin1").hide();
|
||||
$("#NoPluginTip").show();
|
||||
$("#NoPluginTip").css("display", "flex");
|
||||
} else {
|
||||
$("#NoPluginTip").hide();
|
||||
// Only restore login button if not already logged in
|
||||
if ($("#BambuLogin2").is(":hidden")) {
|
||||
$("#BambuLogin1").show();
|
||||
}
|
||||
}
|
||||
} else if (strCmd == "modelmall_model_advise_get") {
|
||||
//alert('hot');
|
||||
@@ -120,8 +135,8 @@ function HandleStudio( pVal )
|
||||
|
||||
m_HotModelList = pVal["hits"];
|
||||
ShowStaffPick(m_HotModelList);
|
||||
} else if (data.cmd === "SetLoginPanelVisibility") {
|
||||
SetLoginPanelVisibility(data.visible);
|
||||
} else if (strCmd == "SetLoginPanelVisibility") {
|
||||
SetLoginPanelVisibility(pVal["data"]["visible"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,32 +161,32 @@ function GotoMenu( strMenu )
|
||||
}
|
||||
}
|
||||
|
||||
function SetLoginInfo( strAvatar, strName )
|
||||
function SetOrcaLoginInfo( strAvatar, strName )
|
||||
{
|
||||
$("#Login1").hide();
|
||||
|
||||
$("#OrcaLogin1").hide();
|
||||
|
||||
$("#UserName").text(strName);
|
||||
|
||||
|
||||
let OriginAvatar=$("#UserAvatarIcon").prop("src");
|
||||
if(strAvatar!=OriginAvatar)
|
||||
if(strAvatar != null && strAvatar.trim() !== '' && strAvatar!=OriginAvatar)
|
||||
$("#UserAvatarIcon").prop("src",strAvatar);
|
||||
else
|
||||
{
|
||||
//alert('Avatar is Same');
|
||||
}
|
||||
|
||||
$("#Login2").show();
|
||||
$("#Login2").css("display","flex");
|
||||
|
||||
$("#OrcaLogin2").show();
|
||||
$("#OrcaLogin2").css("display","flex");
|
||||
}
|
||||
|
||||
function SetUserOffline()
|
||||
function SetOrcaUserOffline()
|
||||
{
|
||||
$("#UserAvatarIcon").prop("src","img/c.jpg");
|
||||
$("#UserName").text('');
|
||||
$("#Login2").hide();
|
||||
|
||||
$("#Login1").show();
|
||||
$("#Login1").css("display","flex");
|
||||
$("#OrcaLogin2").hide();
|
||||
|
||||
$("#OrcaLogin1").show();
|
||||
$("#OrcaLogin1").css("display","flex");
|
||||
}
|
||||
|
||||
function SetMallUrl( strUrl )
|
||||
@@ -246,6 +261,17 @@ function SendMsg_GetLoginInfo()
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
function SendSimpleCommand(command) {
|
||||
var tSend = {};
|
||||
tSend['sequence_id'] = Math.round(new Date() / 1000);
|
||||
tSend['command'] = command;
|
||||
SendWXMessage(JSON.stringify(tSend));
|
||||
}
|
||||
|
||||
function OnOrcaLoginOrRegister() { SendSimpleCommand("homepage_orca_login_or_register"); }
|
||||
function OnOrcaLogOut() { SendSimpleCommand("homepage_orca_logout"); }
|
||||
function SendMsg_GetOrcaLoginInfo() { SendSimpleCommand("get_orca_login_info"); }
|
||||
|
||||
|
||||
function SendMsg_GetRecentFile()
|
||||
{
|
||||
@@ -373,10 +399,52 @@ function OnLogOut()
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_logout";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
// --- Bambu Cloud Section ---
|
||||
|
||||
function ToggleBambuSection() {
|
||||
var body = document.getElementById('BambuCloudBody');
|
||||
var chevron = document.querySelector('.bambu-chevron');
|
||||
if (!body || !chevron) return;
|
||||
bambuSectionExpanded = !bambuSectionExpanded;
|
||||
if (bambuSectionExpanded) {
|
||||
body.classList.add('expanded');
|
||||
chevron.classList.add('expanded');
|
||||
} else {
|
||||
body.classList.remove('expanded');
|
||||
chevron.classList.remove('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
function SetBambuLoginInfo(strAvatar, strName) {
|
||||
$("#BambuLogin1").hide();
|
||||
$("#BambuUserName").text(strName);
|
||||
if (strAvatar && strAvatar.trim() !== '') {
|
||||
$("#BambuAvatarIcon").prop("src", strAvatar);
|
||||
}
|
||||
$("#BambuLogin2").show();
|
||||
$("#BambuLogin2").css("display", "flex");
|
||||
$(".bambu-status-dot").addClass("online");
|
||||
}
|
||||
|
||||
function SetBambuUserOffline() {
|
||||
$("#BambuAvatarIcon").prop("src", "img/c.jpg");
|
||||
$("#BambuUserName").text('');
|
||||
$("#BambuLogin2").hide();
|
||||
if ($("#NoPluginTip").is(":hidden")) {
|
||||
$("#BambuLogin1").show();
|
||||
$("#BambuLogin1").css("display", "flex");
|
||||
}
|
||||
$(".bambu-status-dot").removeClass("online");
|
||||
}
|
||||
|
||||
function OnBambuLoginOrRegister() { SendSimpleCommand("homepage_bambu_login_or_register"); }
|
||||
function OnBambuLogOut() { SendSimpleCommand("homepage_bambu_logout"); }
|
||||
function SendMsg_GetBambuLoginInfo() { SendSimpleCommand("get_bambu_login_info"); }
|
||||
|
||||
function BeginDownloadNetworkPlugin()
|
||||
{
|
||||
var tSend={};
|
||||
@@ -431,19 +499,7 @@ function InitStaffPick()
|
||||
},
|
||||
slidesPerView : 'auto',
|
||||
slidesPerGroup : 3
|
||||
// autoplay: {
|
||||
// delay: 3000,
|
||||
// stopOnLastSlide: false,
|
||||
// disableOnInteraction: true,
|
||||
// disableOnInteraction: false
|
||||
// },
|
||||
// pagination: {
|
||||
// el: '.swiper-pagination',
|
||||
// },
|
||||
// scrollbar: {
|
||||
// el: '.swiper-scrollbar',
|
||||
// draggable: true
|
||||
// }
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
*
|
||||
{
|
||||
padding:0px;
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/*------------------*/
|
||||
body
|
||||
{
|
||||
display:flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-height: 900px;
|
||||
max-width: 1300px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#ErrorBlock
|
||||
{
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#ErrorIcon
|
||||
{
|
||||
width:100px;
|
||||
}
|
||||
|
||||
#ErrorTip
|
||||
{
|
||||
font-size: 18px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
#ErrorBtn
|
||||
{
|
||||
width:100px;
|
||||
display: none;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -1,35 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Cache-Control" content="max-age=7200" />
|
||||
<title>Network Error</title>
|
||||
<link rel="stylesheet" type="text/css" href="../include/global.css" /> <!-- ORCA One for all-->
|
||||
<link rel="stylesheet" type="text/css" href="css/login.css" />
|
||||
<script type="text/javascript" src="js/jquery-3.6.0.min.js"></script>
|
||||
<script type="text/javascript" src="../data/text.js"></script>
|
||||
<script type="text/javascript" src="js/json2.js"></script>
|
||||
<script type="text/javascript" src="js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="js/login.js"></script>
|
||||
</head>
|
||||
<body onLoad="TranslatePage()">
|
||||
<div id="ErrorBlock">
|
||||
<img id="ErrorIcon" src="disconnect3.png" />
|
||||
<div id="ErrorTip" class="trans" title="t40" >Network disconnect, please check and try again later.</div>
|
||||
<div id="ErrorBtn" class="ButtonStyleRegular ButtonTypeChoice">Retry</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/*var TestData={"sequence_id":"0","command":"studio_send_recentfile","data":[{"path":"D:\\work\\Models\\Toy\\3d-puzzle-cube-model_files\\3d-puzzle-cube.3mf","time":"2022\/3\/24 20:33:10"},{"path":"D:\\work\\Models\\Art\\Carved Stone Vase - remeshed+drainage\\Carved Stone Vase.3mf","time":"2022\/3\/24 17:11:51"},{"path":"D:\\work\\Models\\Art\\Kity & Cat\\Cat.3mf","time":"2022\/3\/24 17:07:55"},{"path":"D:\\work\\Models\\Toy\\鐩村墤.3mf","time":"2022\/3\/24 17:06:02"},{"path":"D:\\work\\Models\\Toy\\minimalistic-dual-tone-whistle-model_files\\minimalistic-dual-tone-whistle.3mf","time":"2022\/3\/22 21:12:22"},{"path":"D:\\work\\Models\\Toy\\spiral-city-model_files\\spiral-city.3mf","time":"2022\/3\/22 18:58:37"},{"path":"D:\\work\\Models\\Toy\\impossible-dovetail-puzzle-box-model_files\\impossible-dovetail-puzzle-box.3mf","time":"2022\/3\/22 20:08:40"}]};*/
|
||||
|
||||
|
||||
function OnInit()
|
||||
{
|
||||
TranslatePage();
|
||||
|
||||
$("#HotspotWEB").prop("src","https://www.bambulab.com");
|
||||
}
|
||||
|
||||
|
||||
function HandleStudio( pVal )
|
||||
{
|
||||
let strCmd = pVal['command'];
|
||||
//alert(strCmd);
|
||||
|
||||
if(strCmd=='studio_send_recentfile')
|
||||
{
|
||||
ShowRecentFileList(pVal['data']);
|
||||
}
|
||||
else if(strCmd=='studio_userlogin')
|
||||
{
|
||||
SetLoginInfo(pVal['data']['avatar'],pVal['data']['name']);
|
||||
}
|
||||
else if(strCmd=='studio_useroffline')
|
||||
{
|
||||
SetUserOffline();
|
||||
}
|
||||
else if( strCmd=="studio_set_mallurl" )
|
||||
{
|
||||
SetMallUrl( pVal['data']['url'] );
|
||||
}
|
||||
else if( strCmd=="studio_clickmenu" )
|
||||
{
|
||||
let strName=pVal['data']['menu'];
|
||||
|
||||
GotoMenu(strName);
|
||||
}
|
||||
}
|
||||
|
||||
function GotoMenu( strMenu )
|
||||
{
|
||||
let MenuList=$(".BtnItem");
|
||||
let nAll=MenuList.length;
|
||||
|
||||
for(let n=0;n<nAll;n++)
|
||||
{
|
||||
let OneBtn=MenuList[n];
|
||||
|
||||
if( $(OneBtn).attr("menu")==strMenu )
|
||||
{
|
||||
$(".BtnItem").removeClass("BtnItemSelected");
|
||||
|
||||
$(OneBtn).addClass("BtnItemSelected");
|
||||
|
||||
$("div[board]").hide();
|
||||
$("div[board=\'"+strMenu+"\']").show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function SetLoginInfo( strAvatar, strName )
|
||||
{
|
||||
$("#Login1").hide();
|
||||
|
||||
$("#UserAvatarIcon").prop("src","../../image/cache/"+strAvatar);
|
||||
$("#UserName").text(strName);
|
||||
$("#Login2").show();
|
||||
}
|
||||
|
||||
function SetUserOffline()
|
||||
{
|
||||
$("#UserAvatarIcon").prop("src","img/c.jpg");
|
||||
$("#UserName").text('');
|
||||
$("#Login2").hide();
|
||||
|
||||
$("#Login1").show();
|
||||
}
|
||||
|
||||
function SetMallUrl( strUrl )
|
||||
{
|
||||
$("#MallWeb").prop("src",strUrl);
|
||||
}
|
||||
|
||||
|
||||
function ShowRecentFileList( pList )
|
||||
{
|
||||
let nTotal=pList.length;
|
||||
|
||||
let strHtml='';
|
||||
for(let n=0;n<nTotal;n++)
|
||||
{
|
||||
let OneFile=pList[n];
|
||||
|
||||
let sImg=OneFile["image"];
|
||||
let sPath=OneFile['path'];
|
||||
let sTime=OneFile['time'];
|
||||
|
||||
let index=sPath.lastIndexOf('\\')>0?sPath.lastIndexOf('\\'):sPath.lastIndexOf('\/');
|
||||
let sShortName=sPath.substring(index+1,sPath.length);
|
||||
|
||||
let TmpHtml='<div class="FileItem" onClick="OnOpenRecentFile(\''+ encodeURI(sPath)+'\')" >'+
|
||||
'<a class="FileTip" title="'+sPath+'"></a>'+
|
||||
'<div class="FileImg" ><img src="..\..\img\temp\\'+sImg+'" onerror="this.onerror=null;this.src=\'img/d.png\';" alt="No Image" /></div>'+
|
||||
'<a>'+sShortName+'</a>'+
|
||||
'<div class="FileDate">'+sTime+'</div>'+
|
||||
'</div>';
|
||||
|
||||
strHtml+=TmpHtml;
|
||||
}
|
||||
|
||||
$("#FileList").html(strHtml);
|
||||
}
|
||||
|
||||
|
||||
/*-------MX Message------*/
|
||||
|
||||
function OnLoginOrRegister()
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_login_or_register";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
|
||||
function OnClickNewProject()
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_newproject";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
function OnClickOpenProject()
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_openproject";
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
function OnOpenRecentFile( strPath )
|
||||
{
|
||||
var tSend={};
|
||||
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||
tSend['command']="homepage_open_recentfile";
|
||||
tSend['data']={};
|
||||
tSend['data']['path']=decodeURI(strPath);
|
||||
|
||||
SendWXMessage( JSON.stringify(tSend) );
|
||||
}
|
||||
|
||||
|
||||
@@ -1,927 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OrcaCloud Login</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #f5f5f7;
|
||||
--bg-card: #ffffff;
|
||||
--text-primary: #1d1d1f;
|
||||
--text-secondary: #86868b;
|
||||
--text-tertiary: #6e6e73;
|
||||
--border-color: #d2d2d7;
|
||||
--accent-color: #009688;
|
||||
--accent-hover: #00796b;
|
||||
--error-color: #d32f2f;
|
||||
--success-color: #388e3c;
|
||||
--google-color: #4285f4;
|
||||
--apple-color: #000000;
|
||||
--github-color: #24292e;
|
||||
--input-bg: #f5f5f7;
|
||||
--shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-primary: #1d1d1f;
|
||||
--bg-card: #2d2d2f;
|
||||
--text-primary: #f5f5f7;
|
||||
--text-secondary: #a1a1a6;
|
||||
--text-tertiary: #86868b;
|
||||
--border-color: #424245;
|
||||
--input-bg: #3a3a3c;
|
||||
--shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
box-shadow: var(--shadow);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 40px 32px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
background: var(--input-bg);
|
||||
border-radius: 10px;
|
||||
padding: 4px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tab:hover:not(.active) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
background: var(--input-bg);
|
||||
color: var(--text-primary);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 150, 136, 0.1);
|
||||
}
|
||||
|
||||
.input-group input::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.input-group.error input {
|
||||
border-color: var(--error-color);
|
||||
}
|
||||
|
||||
.input-group .error-text {
|
||||
font-size: 12px;
|
||||
color: var(--error-color);
|
||||
margin-top: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-group.error .error-text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.confirm-password-group {
|
||||
display: none;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.confirm-password-group.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
width: 100%;
|
||||
padding: 14px 24px;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.primary-btn:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.primary-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.primary-btn:disabled {
|
||||
background: var(--text-tertiary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.forgot-link {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: var(--accent-color);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
margin-top: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forgot-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
.divider span {
|
||||
padding: 0 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.providers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.provider-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 12px 24px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.provider-btn:hover {
|
||||
border-color: var(--text-tertiary);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
|
||||
.provider-btn svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.provider-btn.google:hover {
|
||||
border-color: var(--google-color);
|
||||
}
|
||||
|
||||
.provider-btn.apple:hover {
|
||||
border-color: var(--apple-color);
|
||||
}
|
||||
|
||||
.provider-btn.github:hover {
|
||||
border-color: var(--github-color);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
display: block;
|
||||
background: rgba(211, 47, 47, 0.1);
|
||||
color: var(--error-color);
|
||||
border: 1px solid rgba(211, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.message.success {
|
||||
display: block;
|
||||
background: rgba(56, 142, 60, 0.1);
|
||||
color: var(--success-color);
|
||||
border: 1px solid rgba(56, 142, 60, 0.2);
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 16px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.loading-overlay {
|
||||
background: rgba(45, 45, 47, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border-color);
|
||||
border-top-color: var(--accent-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.back-link svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.reset-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reset-view.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.auth-view {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.auth-view.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reset-description {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#debug {
|
||||
margin-top: 20px;
|
||||
font-size: 10px;
|
||||
color: var(--text-tertiary);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
white-space: pre-wrap;
|
||||
display: none;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
background: var(--input-bg);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
<!-- Auth View (Sign In / Sign Up) -->
|
||||
<div id="auth-view" class="auth-view">
|
||||
<div class="header">
|
||||
<svg class="logo" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="64" height="64" rx="14" fill="#009688"/>
|
||||
<path d="M32 16C23.163 16 16 23.163 16 32C16 40.837 23.163 48 32 48C40.837 48 48 40.837 48 32C48 23.163 40.837 16 32 16ZM32 44C25.373 44 20 38.627 20 32C20 25.373 25.373 20 32 20C38.627 20 44 25.373 44 32C44 38.627 38.627 44 32 44Z" fill="white"/>
|
||||
<circle cx="32" cy="32" r="6" fill="white"/>
|
||||
</svg>
|
||||
<h1>OrcaCloud</h1>
|
||||
<p id="header-subtitle">Sign in to sync your settings</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<button class="tab active" data-mode="signin" onclick="setMode('signin')">Sign In</button>
|
||||
<button class="tab" data-mode="signup" onclick="setMode('signup')">Sign Up</button>
|
||||
</div>
|
||||
|
||||
<form id="auth-form" class="form-container" onsubmit="handleSubmit(event)">
|
||||
<div class="input-group" id="email-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" placeholder="you@example.com" autocomplete="email" required>
|
||||
<span class="error-text" id="email-error"></span>
|
||||
</div>
|
||||
|
||||
<div class="input-group" id="password-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" placeholder="Enter your password" autocomplete="current-password" required>
|
||||
<span class="error-text" id="password-error"></span>
|
||||
</div>
|
||||
|
||||
<div class="input-group confirm-password-group" id="confirm-group">
|
||||
<label for="confirm-password">Confirm Password</label>
|
||||
<input type="password" id="confirm-password" placeholder="Confirm your password" autocomplete="new-password">
|
||||
<span class="error-text" id="confirm-error"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="primary-btn" id="submit-btn">Sign In</button>
|
||||
</form>
|
||||
|
||||
<a class="forgot-link" id="forgot-link" onclick="showResetView()">Forgot password?</a>
|
||||
|
||||
<div class="divider"><span>or continue with</span></div>
|
||||
|
||||
<div class="providers">
|
||||
<button class="provider-btn google" onclick="handleOAuthProvider('google')">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
|
||||
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
||||
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
||||
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
||||
</svg>
|
||||
Continue with Google
|
||||
</button>
|
||||
|
||||
<button class="provider-btn apple" onclick="handleOAuthProvider('apple')">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" fill="currentColor"/>
|
||||
</svg>
|
||||
Continue with Apple
|
||||
</button>
|
||||
|
||||
<button class="provider-btn github" onclick="handleOAuthProvider('github')">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" fill="currentColor"/>
|
||||
</svg>
|
||||
Continue with GitHub
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password View -->
|
||||
<div id="reset-view" class="reset-view">
|
||||
<a class="back-link" onclick="hideResetView()">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Back to sign in
|
||||
</a>
|
||||
|
||||
<div class="header">
|
||||
<h1>Reset Password</h1>
|
||||
</div>
|
||||
|
||||
<p class="reset-description">
|
||||
Enter your email address and we'll send you a link to reset your password.
|
||||
</p>
|
||||
|
||||
<form id="reset-form" class="form-container" onsubmit="handleResetSubmit(event)">
|
||||
<div class="input-group" id="reset-email-group">
|
||||
<label for="reset-email">Email</label>
|
||||
<input type="email" id="reset-email" placeholder="you@example.com" autocomplete="email" required>
|
||||
<span class="error-text" id="reset-email-error"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="primary-btn" id="reset-btn">Send Reset Link</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Message Display -->
|
||||
<div id="message" class="message"></div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loading" class="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
<span class="loading-text" id="loading-text">Signing in...</span>
|
||||
</div>
|
||||
|
||||
<!-- Debug Output -->
|
||||
<div id="debug"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Configuration (populated from C++ via get_login_cmd)
|
||||
let config = {
|
||||
backend_url: '',
|
||||
apikey: '',
|
||||
pkce: null
|
||||
};
|
||||
|
||||
// State
|
||||
let currentMode = 'signin'; // 'signin' | 'signup'
|
||||
|
||||
// Debug logging
|
||||
function log(msg) {
|
||||
console.log(msg);
|
||||
var d = document.getElementById('debug');
|
||||
if (d) {
|
||||
// Uncomment the next line to show debug panel
|
||||
// d.style.display = 'block';
|
||||
d.innerText += new Date().toISOString().substr(11, 8) + ' ' + msg + '\n';
|
||||
d.scrollTop = d.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to C++ via JavaScript bridge
|
||||
function sendMessage(cmd, data) {
|
||||
log('Sending: ' + cmd);
|
||||
var msg = JSON.stringify({ command: cmd, data: data || {} });
|
||||
try {
|
||||
if (window.wx) {
|
||||
window.wx.postMessage(msg);
|
||||
} else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.wx) {
|
||||
window.webkit.messageHandlers.wx.postMessage(msg);
|
||||
} else {
|
||||
log('Error: No bridge found (wx or webkit)');
|
||||
}
|
||||
} catch (e) {
|
||||
log('Send error: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle messages from C++
|
||||
window.addEventListener('message', function(event) {
|
||||
log('Received message');
|
||||
var msg = event.data;
|
||||
if (typeof msg === 'string') {
|
||||
try {
|
||||
msg = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
log('Parse error: ' + e.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log('Message action: ' + (msg.action || msg.command || 'unknown'));
|
||||
|
||||
// Handle login config from C++ (received once on page load)
|
||||
if (msg.action === 'login_config') {
|
||||
handleLoginConfig(msg);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Tab switching
|
||||
function setMode(mode) {
|
||||
currentMode = mode;
|
||||
clearMessage();
|
||||
clearErrors();
|
||||
|
||||
// Update tabs
|
||||
document.querySelectorAll('.tab').forEach(function(tab) {
|
||||
tab.classList.toggle('active', tab.dataset.mode === mode);
|
||||
});
|
||||
|
||||
// Update form
|
||||
var confirmGroup = document.getElementById('confirm-group');
|
||||
var submitBtn = document.getElementById('submit-btn');
|
||||
var forgotLink = document.getElementById('forgot-link');
|
||||
var subtitle = document.getElementById('header-subtitle');
|
||||
var passwordInput = document.getElementById('password');
|
||||
|
||||
if (mode === 'signup') {
|
||||
confirmGroup.classList.add('visible');
|
||||
submitBtn.textContent = 'Create Account';
|
||||
forgotLink.style.display = 'none';
|
||||
subtitle.textContent = 'Create your account';
|
||||
passwordInput.autocomplete = 'new-password';
|
||||
} else {
|
||||
confirmGroup.classList.remove('visible');
|
||||
submitBtn.textContent = 'Sign In';
|
||||
forgotLink.style.display = 'block';
|
||||
subtitle.textContent = 'Sign in to sync your settings';
|
||||
passwordInput.autocomplete = 'current-password';
|
||||
}
|
||||
}
|
||||
|
||||
// Form submission - calls Supabase directly
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
clearMessage();
|
||||
clearErrors();
|
||||
|
||||
var email = document.getElementById('email').value.trim();
|
||||
var password = document.getElementById('password').value;
|
||||
|
||||
// Validate email
|
||||
if (!isValidEmail(email)) {
|
||||
showFieldError('email', 'Please enter a valid email address');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate password
|
||||
if (password.length < 6) {
|
||||
showFieldError('password', 'Password must be at least 6 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMode === 'signup') {
|
||||
var confirmPassword = document.getElementById('confirm-password').value;
|
||||
if (password !== confirmPassword) {
|
||||
showFieldError('confirm', 'Passwords do not match');
|
||||
return;
|
||||
}
|
||||
await handlePasswordSignup(email, password);
|
||||
} else {
|
||||
await handlePasswordLogin(email, password);
|
||||
}
|
||||
}
|
||||
|
||||
// Password login - direct Supabase call
|
||||
async function handlePasswordLogin(email, password) {
|
||||
if (!config.backend_url || !config.apikey) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Signing in...');
|
||||
|
||||
try {
|
||||
var url = config.backend_url + '/auth/v1/token?grant_type=password';
|
||||
var response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': config.apikey
|
||||
},
|
||||
body: JSON.stringify({ email: email, password: password })
|
||||
});
|
||||
|
||||
var data = await response.json();
|
||||
|
||||
if (response.ok && data.access_token) {
|
||||
// Success - send tokens to C++
|
||||
sendUserLogin(data);
|
||||
} else {
|
||||
hideLoading();
|
||||
var errorMsg = data.error_description || data.msg || data.error || 'Login failed';
|
||||
showMessage(errorMsg, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoading();
|
||||
log('Login error: ' + err.toString());
|
||||
showMessage('Network error. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Password signup - direct Supabase call
|
||||
async function handlePasswordSignup(email, password) {
|
||||
if (!config.backend_url || !config.apikey) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Creating account...');
|
||||
|
||||
try {
|
||||
var url = config.backend_url + '/auth/v1/signup';
|
||||
var response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': config.apikey
|
||||
},
|
||||
body: JSON.stringify({ email: email, password: password })
|
||||
});
|
||||
|
||||
var data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (data.access_token) {
|
||||
// Auto-confirmed - send tokens to C++
|
||||
sendUserLogin(data);
|
||||
} else {
|
||||
// Email verification required
|
||||
hideLoading();
|
||||
showMessage('Account created! Please check your email to verify your account.', 'success');
|
||||
}
|
||||
} else {
|
||||
hideLoading();
|
||||
var errorMsg = data.error_description || data.msg || data.error || 'Signup failed';
|
||||
showMessage(errorMsg, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoading();
|
||||
log('Signup error: ' + err.toString());
|
||||
showMessage('Network error. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Password reset - direct Supabase call
|
||||
async function handleResetSubmit(e) {
|
||||
e.preventDefault();
|
||||
clearMessage();
|
||||
|
||||
var email = document.getElementById('reset-email').value.trim();
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
document.getElementById('reset-email-group').classList.add('error');
|
||||
document.getElementById('reset-email-error').textContent = 'Please enter a valid email address';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.backend_url || !config.apikey) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Sending reset link...');
|
||||
|
||||
try {
|
||||
var url = config.backend_url + '/auth/v1/recover';
|
||||
var response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'apikey': config.apikey
|
||||
},
|
||||
body: JSON.stringify({ email: email })
|
||||
});
|
||||
|
||||
hideLoading();
|
||||
|
||||
// Supabase returns 200 even if email doesn't exist (for security)
|
||||
if (response.ok) {
|
||||
showMessage('Password reset email sent. Please check your inbox.', 'success');
|
||||
} else {
|
||||
showMessage('Failed to send reset email. Please try again.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
hideLoading();
|
||||
log('Reset error: ' + err.toString());
|
||||
showMessage('Network error. Please check your connection.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Send successful login to C++ (unified for all auth methods)
|
||||
function sendUserLogin(data) {
|
||||
var user = data.user || {};
|
||||
var userMeta = user.user_metadata || {};
|
||||
|
||||
sendMessage('user_login', {
|
||||
token: data.access_token,
|
||||
refresh_token: data.refresh_token || '',
|
||||
user_id: user.id || '',
|
||||
username: userMeta.preferred_username || user.email || '',
|
||||
name: userMeta.full_name || userMeta.name || user.email || '',
|
||||
nickname: userMeta.preferred_username || userMeta.name || '',
|
||||
avatar: userMeta.avatar_url || '',
|
||||
state: config.pkce ? config.pkce.state : ''
|
||||
});
|
||||
}
|
||||
|
||||
// OAuth provider click - builds URL directly using stored config
|
||||
function handleOAuthProvider(provider) {
|
||||
log('OAuth provider: ' + provider);
|
||||
|
||||
if (!config.backend_url || !config.pkce) {
|
||||
showMessage('Configuration not loaded. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading('Connecting to ' + provider.charAt(0).toUpperCase() + provider.slice(1) + '...');
|
||||
|
||||
var pkce = config.pkce;
|
||||
|
||||
// Construct Supabase Authorize URL
|
||||
var url = config.backend_url + '/auth/v1/authorize';
|
||||
var params = new URLSearchParams();
|
||||
|
||||
params.append('provider', provider);
|
||||
params.append('code_challenge', pkce.code_challenge);
|
||||
params.append('code_challenge_method', pkce.code_challenge_method);
|
||||
params.append('redirect_uri', pkce.redirect_uri);
|
||||
params.append('state', pkce.state);
|
||||
params.append('response_type', 'code');
|
||||
|
||||
var fullUrl = url + '?' + params.toString();
|
||||
|
||||
document.getElementById('loading-text').textContent = 'Opening browser for secure login...';
|
||||
|
||||
// Request C++ to open this URL in system browser
|
||||
sendMessage('thirdparty_login', { url: fullUrl });
|
||||
|
||||
// Show message after a delay
|
||||
setTimeout(function() {
|
||||
hideLoading();
|
||||
showMessage('Browser opened. Complete login there, then this window will close automatically.', 'success');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Handle login config from C++ (received once on page load)
|
||||
function handleLoginConfig(msg) {
|
||||
log('Received login config');
|
||||
|
||||
// Store config for all auth methods
|
||||
config.backend_url = msg.backend_url || config.backend_url;
|
||||
config.apikey = msg.apikey || config.apikey;
|
||||
config.pkce = msg.pkce || config.pkce;
|
||||
|
||||
log('Config stored: backend_url=' + config.backend_url);
|
||||
}
|
||||
|
||||
// View switching
|
||||
function showResetView() {
|
||||
document.getElementById('auth-view').classList.add('hidden');
|
||||
document.getElementById('reset-view').classList.add('visible');
|
||||
clearMessage();
|
||||
document.getElementById('reset-email').value = document.getElementById('email').value;
|
||||
}
|
||||
|
||||
function hideResetView() {
|
||||
document.getElementById('auth-view').classList.remove('hidden');
|
||||
document.getElementById('reset-view').classList.remove('visible');
|
||||
clearMessage();
|
||||
}
|
||||
|
||||
// Loading state
|
||||
function showLoading(text) {
|
||||
document.getElementById('loading').classList.add('visible');
|
||||
document.getElementById('loading-text').textContent = text || 'Loading...';
|
||||
disableForm(true);
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
document.getElementById('loading').classList.remove('visible');
|
||||
disableForm(false);
|
||||
}
|
||||
|
||||
function disableForm(disabled) {
|
||||
document.querySelectorAll('input, button').forEach(function(el) {
|
||||
el.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
// Messages
|
||||
function showMessage(text, type) {
|
||||
var msg = document.getElementById('message');
|
||||
msg.textContent = text;
|
||||
msg.className = 'message ' + type;
|
||||
}
|
||||
|
||||
function clearMessage() {
|
||||
var msg = document.getElementById('message');
|
||||
msg.textContent = '';
|
||||
msg.className = 'message';
|
||||
}
|
||||
|
||||
// Field errors
|
||||
function showFieldError(field, message) {
|
||||
var groupId = field + '-group';
|
||||
var errorId = field + '-error';
|
||||
var group = document.getElementById(groupId);
|
||||
var error = document.getElementById(errorId);
|
||||
if (group) group.classList.add('error');
|
||||
if (error) error.textContent = message;
|
||||
}
|
||||
|
||||
function clearErrors() {
|
||||
document.querySelectorAll('.input-group').forEach(function(group) {
|
||||
group.classList.remove('error');
|
||||
});
|
||||
document.querySelectorAll('.error-text').forEach(function(error) {
|
||||
error.textContent = '';
|
||||
});
|
||||
}
|
||||
|
||||
// Validation
|
||||
function isValidEmail(email) {
|
||||
var re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return re.test(email);
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
window.onload = function() {
|
||||
log('Window loaded');
|
||||
// Request login config from C++ to get backend URL and API key
|
||||
setTimeout(function() {
|
||||
sendMessage('get_login_cmd');
|
||||
}, 300);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Executable → Regular
@@ -75,7 +75,7 @@ void generate_custom_presets(PresetBundle* preset_bundle, AppConfig& app_config)
|
||||
fs::path folder(dir_user_presets);
|
||||
if (!fs::exists(folder))
|
||||
fs::create_directory(folder);
|
||||
std::vector<std::string> need_to_delete_list; // store setting ids of preset
|
||||
std::map<std::string, std::string> need_to_delete_list; // store setting ids of preset
|
||||
|
||||
preset_bundle->prints.save_user_presets(dir_user_presets, PRESET_PRINT_NAME, need_to_delete_list);
|
||||
preset_bundle->filaments.save_user_presets(dir_user_presets, PRESET_FILAMENT_NAME, need_to_delete_list);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "format.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
@@ -320,6 +321,11 @@ void AppConfig::set_defaults()
|
||||
if (get("allow_abnormal_storage").empty()) {
|
||||
set_bool("allow_abnormal_storage", false);
|
||||
}
|
||||
|
||||
if (get("preset_bundle_auto_update").empty()) {
|
||||
set_bool("preset_bundle_auto_update", false);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
if (get(SETTING_USE_ENCRYPTED_TOKEN_FILE).empty())
|
||||
set_bool(SETTING_USE_ENCRYPTED_TOKEN_FILE, true);
|
||||
@@ -357,6 +363,10 @@ void AppConfig::set_defaults()
|
||||
set_bool("show_daily_tips", true);
|
||||
}
|
||||
|
||||
if (get("show_shared_profiles_notification").empty()) {
|
||||
set_bool("show_shared_profiles_notification", true);
|
||||
}
|
||||
|
||||
if (get("auto_calculate_flush").empty()){
|
||||
set("auto_calculate_flush","all");
|
||||
}
|
||||
@@ -802,6 +812,18 @@ std::string AppConfig::load()
|
||||
if (it_section->second.empty())
|
||||
m_storage.erase(it_section);
|
||||
}
|
||||
|
||||
// Default for new installs
|
||||
if (get(SETTING_CLOUD_PROVIDERS).empty()) {
|
||||
// Migrate add bbl cloud if installed_networking is true
|
||||
bool enable_bbl_cloud = get_bool("installed_networking");
|
||||
if (enable_bbl_cloud) {
|
||||
// Legacy Bambu-only user: give them both providers
|
||||
set(SETTING_CLOUD_PROVIDERS, "orca;bbl");
|
||||
} else {
|
||||
set(SETTING_CLOUD_PROVIDERS, "orca");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override missing or keys with their defaults.
|
||||
@@ -1572,6 +1594,62 @@ void AppConfig::clear_remind_network_update_later()
|
||||
set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, false);
|
||||
}
|
||||
|
||||
std::vector<std::string> AppConfig::get_cloud_providers() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string providers = get(SETTING_CLOUD_PROVIDERS);
|
||||
if (providers.empty()) {
|
||||
result.push_back("orca");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::stringstream ss(providers);
|
||||
std::string provider;
|
||||
while (std::getline(ss, provider, ';')) {
|
||||
if (!provider.empty())
|
||||
result.push_back(provider);
|
||||
}
|
||||
// Ensure "orca" is always present
|
||||
if (std::find(result.begin(), result.end(), "orca") == result.end()) {
|
||||
result.insert(result.begin(), "orca");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppConfig::set_cloud_providers(const std::vector<std::string>& providers)
|
||||
{
|
||||
std::string joined;
|
||||
for (size_t i = 0; i < providers.size(); ++i) {
|
||||
if (i > 0) joined += ";";
|
||||
joined += providers[i];
|
||||
}
|
||||
set(SETTING_CLOUD_PROVIDERS, joined);
|
||||
}
|
||||
|
||||
bool AppConfig::has_cloud_provider(const std::string& provider) const
|
||||
{
|
||||
auto providers = get_cloud_providers();
|
||||
return std::find(providers.begin(), providers.end(), provider) != providers.end();
|
||||
}
|
||||
|
||||
void AppConfig::add_cloud_provider(const std::string& provider)
|
||||
{
|
||||
auto providers = get_cloud_providers();
|
||||
if (std::find(providers.begin(), providers.end(), provider) == providers.end()) {
|
||||
providers.push_back(provider);
|
||||
set_cloud_providers(providers);
|
||||
}
|
||||
}
|
||||
|
||||
void AppConfig::remove_cloud_provider(const std::string& provider)
|
||||
{
|
||||
if (provider == "orca")
|
||||
return; // Cannot remove orca
|
||||
auto providers = get_cloud_providers();
|
||||
providers.erase(std::remove(providers.begin(), providers.end(), provider), providers.end());
|
||||
set_cloud_providers(providers);
|
||||
}
|
||||
|
||||
void AppConfig::reset_selections()
|
||||
{
|
||||
auto it = m_storage.find("presets");
|
||||
|
||||
@@ -29,6 +29,7 @@ using namespace nlohmann;
|
||||
#define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled"
|
||||
#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later"
|
||||
#define SETTING_USE_ENCRYPTED_TOKEN_FILE "use_encrypted_token_file"
|
||||
#define SETTING_CLOUD_PROVIDERS "cloud_providers"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
||||
@@ -374,6 +375,13 @@ public:
|
||||
void set_remind_network_update_later(bool remind);
|
||||
void clear_remind_network_update_later();
|
||||
|
||||
// Cloud providers (semicolon-delimited, e.g. "orca;bambu")
|
||||
std::vector<std::string> get_cloud_providers() const;
|
||||
void set_cloud_providers(const std::vector<std::string>& providers);
|
||||
bool has_cloud_provider(const std::string& provider) const;
|
||||
void add_cloud_provider(const std::string& provider);
|
||||
void remove_cloud_provider(const std::string& provider);
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const
|
||||
|
||||
+240
-83
@@ -52,6 +52,78 @@ using boost::property_tree::ptree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ParsedName {
|
||||
PresetOrigin::Kind kind { PresetOrigin::Kind::User };
|
||||
std::string bundle_id;
|
||||
std::string bare;
|
||||
};
|
||||
|
||||
// Canonical names are built in-memory with '/' separators, so a straight prefix+split match is enough.
|
||||
static ParsedName parse_preset_name(const std::string &raw_name)
|
||||
{
|
||||
ParsedName out;
|
||||
|
||||
auto try_prefix = [&](const char *dir, PresetOrigin::Kind kind) {
|
||||
const std::string prefix = std::string(dir) + "/";
|
||||
if (! boost::starts_with(raw_name, prefix))
|
||||
return false;
|
||||
const size_t id_start = prefix.size();
|
||||
const size_t id_end = raw_name.find('/', id_start);
|
||||
if (id_end == std::string::npos || id_end == id_start)
|
||||
return false;
|
||||
out.kind = kind;
|
||||
out.bundle_id = raw_name.substr(id_start, id_end - id_start);
|
||||
out.bare = raw_name.substr(id_end + 1);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (! try_prefix(PRESET_LOCAL_DIR, PresetOrigin::Kind::LocalBundle) &&
|
||||
! try_prefix(PRESET_SUBSCRIBED_DIR, PresetOrigin::Kind::SubscribedBundle))
|
||||
out.bare = raw_name;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin)
|
||||
{
|
||||
switch (origin.kind) {
|
||||
case PresetOrigin::Kind::LocalBundle:
|
||||
return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_LOCAL_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name;
|
||||
case PresetOrigin::Kind::SubscribedBundle:
|
||||
return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_SUBSCRIBED_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name;
|
||||
default:
|
||||
return preset_bare_name;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_preset_bare_name(const std::string &canonical_name)
|
||||
{
|
||||
const auto pos = canonical_name.find_last_of('/');
|
||||
return pos == std::string::npos ? canonical_name : canonical_name.substr(pos + 1);
|
||||
}
|
||||
|
||||
PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin)
|
||||
{
|
||||
if (explicit_origin.kind != PresetOrigin::Kind::Auto)
|
||||
return explicit_origin;
|
||||
|
||||
for (auto it = path.begin(); it != path.end(); ++ it) {
|
||||
const auto next = std::next(it);
|
||||
if (next == path.end())
|
||||
break;
|
||||
const std::string segment = it->string();
|
||||
if (segment == PRESET_LOCAL_DIR)
|
||||
return PresetOrigin(PresetOrigin::Kind::LocalBundle, next->string());
|
||||
if (segment == PRESET_SUBSCRIBED_DIR)
|
||||
return PresetOrigin(PresetOrigin::Kind::SubscribedBundle, next->string());
|
||||
}
|
||||
return PresetOrigin(PresetOrigin::Kind::User);
|
||||
}
|
||||
|
||||
//BBS: add a function to load the version from xxx.json
|
||||
Semver get_version_from_json(std::string file_path)
|
||||
{
|
||||
@@ -522,7 +594,7 @@ void Preset::load_info(const std::string& file)
|
||||
void Preset::save_info(std::string file)
|
||||
{
|
||||
//BBS: add project embedded preset logic
|
||||
if (this->is_project_embedded)
|
||||
if (this->is_project_embedded || this->is_from_bundle())
|
||||
return;
|
||||
if (file.empty()) {
|
||||
fs::path idx_file(this->file);
|
||||
@@ -544,17 +616,26 @@ void Preset::save_info(std::string file)
|
||||
c.close();
|
||||
}
|
||||
|
||||
void Preset::remove_files()
|
||||
void Preset::remove_files(bool cloud_already_deleted)
|
||||
{
|
||||
//BBS: add project embedded preset logic
|
||||
if (this->is_project_embedded)
|
||||
if (this->is_project_embedded) {
|
||||
return;
|
||||
}
|
||||
// Erase the preset file.
|
||||
boost::nowide::remove(this->file.c_str());
|
||||
fs::path idx_path(this->file);
|
||||
idx_path.replace_extension(".info");
|
||||
if (fs::exists(idx_path))
|
||||
boost::nowide::remove(idx_path.string().c_str());
|
||||
if (fs::exists(idx_path)) {
|
||||
if (!this->setting_id.empty() && !cloud_already_deleted) {
|
||||
// Cloud-synced preset - mark for deletion and keep .info file until sync confirms
|
||||
this->sync_info = "delete";
|
||||
this->save_info(idx_path.string());
|
||||
} else {
|
||||
// Local-only preset or cloud already confirmed deletion - safe to delete .info immediately
|
||||
boost::nowide::remove(idx_path.string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//BBS: add logic for only difference save
|
||||
@@ -570,12 +651,15 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
from_str = std::string("User");
|
||||
else if (this->is_project_embedded)
|
||||
from_str = std::string("Project");
|
||||
else if (this->is_from_bundle())
|
||||
from_str = std::string("Bundle");
|
||||
else if (this->is_system)
|
||||
from_str = std::string("System");
|
||||
else
|
||||
from_str = std::string("Default");
|
||||
|
||||
boost::filesystem::create_directories(fs::path(this->file).parent_path());
|
||||
const std::string bare_name = get_preset_bare_name(this->name);
|
||||
|
||||
//BBS: only save difference if it has parent
|
||||
if (parent_config) {
|
||||
@@ -615,19 +699,22 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
opt_dst->set(opt_src);
|
||||
}
|
||||
}
|
||||
temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string());
|
||||
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
|
||||
} else if (!filament_id.empty() && inherits().empty()) {
|
||||
DynamicPrintConfig temp_config = config;
|
||||
temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id));
|
||||
temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string());
|
||||
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
|
||||
} else {
|
||||
this->config.save_to_json(this->file, this->name, from_str, this->version.to_string());
|
||||
this->config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id;
|
||||
|
||||
fs::path idx_file(this->file);
|
||||
idx_file.replace_extension(".info");
|
||||
this->save_info(idx_file.string());
|
||||
// Bundle presets are synced via bundle_id and don't need individual .info files.
|
||||
if (! this->is_from_bundle()) {
|
||||
fs::path idx_file(this->file);
|
||||
idx_file.replace_extension(".info");
|
||||
this->save_info(idx_file.string());
|
||||
}
|
||||
}
|
||||
|
||||
void Preset::reload(Preset const &parent)
|
||||
@@ -1205,19 +1292,34 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
|
||||
++ m_num_default_presets;
|
||||
}
|
||||
|
||||
std::string PresetCollection::canonical_preset_name(const std::string &name, const PresetOrigin &load_origin) const
|
||||
{
|
||||
const ParsedName parsed = parse_preset_name(name);
|
||||
PresetOrigin origin = load_origin;
|
||||
if (origin.kind == PresetOrigin::Kind::Auto) {
|
||||
origin.kind = parsed.kind;
|
||||
origin.bundle_id = parsed.bundle_id;
|
||||
} else if (origin.is_bundle() && origin.bundle_id.empty()) {
|
||||
origin.bundle_id = parsed.bundle_id;
|
||||
}
|
||||
return get_preset_canonical_name(parsed.bare, origin);
|
||||
}
|
||||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(
|
||||
const std::string &dir_path, const std::string &subdir,
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
std::function<void(Preset&)> preset_loaded_fn, const PresetOrigin &load_origin)
|
||||
{
|
||||
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred();
|
||||
const PresetOrigin resolved_origin = detect_origin_from_path(dir, load_origin);
|
||||
|
||||
// Load custom roots first
|
||||
if (fs::exists(dir / "base")) {
|
||||
load_presets(dir.string(), "base", substitutions, substitution_rule);
|
||||
load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin);
|
||||
}
|
||||
|
||||
//BBS: add config related logs
|
||||
@@ -1247,14 +1349,16 @@ void PresetCollection::load_presets(
|
||||
if (Slic3r::is_json_file(file_name)) {
|
||||
// Remove the .ini suffix.
|
||||
std::string name = file_name.erase(file_name.size() - 5);
|
||||
if (this->find_preset(name, false)) {
|
||||
std::string canonical_name = this->canonical_preset_name(name, resolved_origin);
|
||||
if (this->find_preset(canonical_name, false)) {
|
||||
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
|
||||
// that's already been loaded from a bundle.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Preset preset(m_type, name, false);
|
||||
Preset preset(m_type, canonical_name, false);
|
||||
preset.bundle_id = resolved_origin.bundle_id;
|
||||
preset.file = dir_entry.path().string();
|
||||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
@@ -1305,7 +1409,6 @@ void PresetCollection::load_presets(
|
||||
std::string inherits_value = option_str->value;
|
||||
// Orca: try to find if the parent preset has been renamed
|
||||
inherit_preset = this->find_preset2(inherits_value);
|
||||
|
||||
} else {
|
||||
;
|
||||
}
|
||||
@@ -1383,6 +1486,9 @@ void PresetCollection::load_presets(
|
||||
fs::remove(file_path);
|
||||
}
|
||||
|
||||
if (preset_loaded_fn != nullptr)
|
||||
preset_loaded_fn(preset);
|
||||
|
||||
presets_loaded.emplace_back(preset);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
|
||||
} catch (const std::runtime_error &err) {
|
||||
@@ -1554,7 +1660,7 @@ void PresetCollection::load_project_embedded_presets(std::vector<Preset*>& proje
|
||||
inherits_value.replace(pos, 1, 1, '~');
|
||||
option_str->value = inherits_value;
|
||||
}*/
|
||||
inherit_preset = this->find_preset(inherits_value, false, true);
|
||||
inherit_preset = this->find_preset2(inherits_value, true);
|
||||
}
|
||||
const Preset& default_preset = this->default_preset_for(config);
|
||||
if (inherit_preset) {
|
||||
@@ -1655,9 +1761,10 @@ bool PresetCollection::reset_project_embedded_presets()
|
||||
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time)
|
||||
{
|
||||
lock();
|
||||
const std::string canonical_name = this->canonical_preset_name(name);
|
||||
for (auto it = m_presets.begin(); it != m_presets.end(); it++) {
|
||||
Preset* preset = &m_presets[it - m_presets.begin()];
|
||||
if (preset->name == name) {
|
||||
if (preset->name == canonical_name) {
|
||||
if (syncinfo.empty())
|
||||
preset->sync_info.clear();
|
||||
else
|
||||
@@ -1722,7 +1829,7 @@ void PresetCollection::update_user_presets_directory(const std::string& dir_path
|
||||
}
|
||||
|
||||
//BBS: save user presets to local
|
||||
void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::vector<std::string>& need_to_delete_list)
|
||||
void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::map<std::string, std::string>& need_to_delete_list)
|
||||
{
|
||||
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred();
|
||||
|
||||
@@ -1776,31 +1883,37 @@ void PresetCollection::save_user_presets(const std::string& dir_path, const std:
|
||||
}
|
||||
|
||||
//BBS: load one user preset from key-values
|
||||
bool PresetCollection::load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule)
|
||||
bool PresetCollection::load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin)
|
||||
{
|
||||
std::string errors_cummulative;
|
||||
// Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken.
|
||||
// (see the "Preset already present, not loading" message).
|
||||
//std::deque<Preset> presets_loaded;
|
||||
int count = 0;
|
||||
const std::string canonical_name = this->canonical_preset_name(name, load_origin);
|
||||
auto update_alias = [this](Preset &preset) {
|
||||
if (! preset.alias.empty())
|
||||
return;
|
||||
set_custom_preset_alias(preset);
|
||||
};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%name %preset_values.size();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%canonical_name %preset_values.size();
|
||||
|
||||
//if the version is not matching, skip it
|
||||
if (preset_values.find(BBL_JSON_KEY_VERSION) == preset_values.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%canonical_name;
|
||||
return false;
|
||||
}
|
||||
std::string version_str = preset_values[BBL_JSON_KEY_VERSION];
|
||||
boost::optional<Semver> cloud_version = Semver::parse(version_str);
|
||||
if (!cloud_version) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %canonical_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
//setting_id
|
||||
if (preset_values.find(BBL_JSON_KEY_SETTING_ID) == preset_values.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%canonical_name;
|
||||
return false;
|
||||
}
|
||||
std::string cloud_setting_id = preset_values[BBL_JSON_KEY_SETTING_ID];
|
||||
@@ -1813,17 +1926,17 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
|
||||
//user_id
|
||||
if (preset_values.find(BBL_JSON_KEY_USER_ID) == preset_values.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find user_id, not loading for user preset %1%")%name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find user_id, not loading for user preset %1%")%canonical_name;
|
||||
return false;
|
||||
}
|
||||
std::string cloud_user_id = preset_values[BBL_JSON_KEY_USER_ID];
|
||||
|
||||
lock();
|
||||
//std::string name = preset->name;
|
||||
auto iter = this->find_preset_internal(name);
|
||||
auto iter = this->find_preset_internal(canonical_name);
|
||||
bool need_update = false;
|
||||
if ((iter != m_presets.end()) && (iter->name == name)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << name;
|
||||
if ((iter != m_presets.end()) && (iter->name == canonical_name)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << canonical_name;
|
||||
//BBS: we should compare the time between cloud and local
|
||||
if ((cloud_update_time == 0) || (cloud_update_time <= iter->updated_time)) {
|
||||
if (cloud_update_time < iter->updated_time)
|
||||
@@ -1835,7 +1948,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
fs::path idx_file(iter->file);
|
||||
idx_file.replace_extension(".info");
|
||||
iter->save_info(idx_file.string());
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%name %cloud_update_time %iter->updated_time;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%canonical_name %cloud_update_time %iter->updated_time;
|
||||
unlock();
|
||||
return false;
|
||||
}
|
||||
@@ -1847,7 +1960,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
|
||||
// base_id
|
||||
if (preset_values.find(BBL_JSON_KEY_BASE_ID) == preset_values.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % canonical_name;
|
||||
unlock();
|
||||
return false;
|
||||
}
|
||||
@@ -1857,14 +1970,14 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
std::string cloud_filament_id;
|
||||
if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) {
|
||||
cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID];
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << canonical_name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id;
|
||||
}
|
||||
|
||||
DynamicPrintConfig new_config, cloud_config;
|
||||
try {
|
||||
ConfigSubstitutions config_substitutions = cloud_config.load_string_map(preset_values, rule);
|
||||
if (! config_substitutions.empty())
|
||||
substitutions.push_back({ name, m_type, PresetConfigSubstitutions::Source::UserCloud, name, std::move(config_substitutions) });
|
||||
substitutions.push_back({ canonical_name, m_type, PresetConfigSubstitutions::Source::UserCloud, canonical_name, std::move(config_substitutions) });
|
||||
|
||||
//BBS: use inherit config as the base
|
||||
Preset* inherit_preset = nullptr;
|
||||
@@ -1872,12 +1985,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
if (inherits_config) {
|
||||
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
|
||||
std::string inherits_value = option_str->value;
|
||||
/*size_t pos = inherits_value.find_first_of('*');
|
||||
if (pos != std::string::npos) {
|
||||
inherits_value.replace(pos, 1, 1, '~');
|
||||
option_str->value = inherits_value;
|
||||
}*/
|
||||
inherit_preset = this->find_preset(inherits_value, false, true);
|
||||
inherit_preset = this->find_preset2(inherits_value, true);
|
||||
}
|
||||
const Preset& default_preset = this->default_preset_for(cloud_config);
|
||||
if (inherit_preset) {
|
||||
@@ -1891,7 +1999,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
|
||||
if (inherits_config2 && !inherits_config2->value.empty()) {
|
||||
//we should skip this preset here
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%canonical_name;
|
||||
unlock();
|
||||
return false;
|
||||
}
|
||||
@@ -1917,7 +2025,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
std::string incorrect_keys = Preset::remove_invalid_keys(new_config, default_preset.config);
|
||||
if (!incorrect_keys.empty()) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << name
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << canonical_name
|
||||
<< "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
|
||||
}
|
||||
if (need_update) {
|
||||
@@ -1933,15 +2041,17 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
iter->setting_id = cloud_setting_id;
|
||||
iter->base_id = cloud_base_id;
|
||||
iter->filament_id = cloud_filament_id;
|
||||
update_alias(*iter);
|
||||
//presets_loaded.emplace_back(*it->second);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
|
||||
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
|
||||
}
|
||||
else {
|
||||
//create a new one
|
||||
Preset preset(m_type, name, false);
|
||||
Preset preset(m_type, canonical_name, false);
|
||||
preset.is_system = false;
|
||||
preset.loaded = true;
|
||||
preset.bundle_id = load_origin.bundle_id;
|
||||
preset.config = new_config;
|
||||
preset.updated_time = cloud_update_time;
|
||||
preset.sync_info = "save";
|
||||
@@ -1950,6 +2060,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
preset.setting_id = cloud_setting_id;
|
||||
preset.base_id = cloud_base_id;
|
||||
preset.filament_id = cloud_filament_id;
|
||||
update_alias(preset);
|
||||
|
||||
size_t cur_index = iter - m_presets.begin();
|
||||
m_presets.insert(iter, preset);
|
||||
@@ -1971,7 +2082,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
if (! errors_cummulative.empty())
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, load user preset %1% , type %2%, errors_cummulative %3%")%name %Preset::get_type_string(m_type) %errors_cummulative;
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, load user preset %1% , type %2%, errors_cummulative %3%")%canonical_name %Preset::get_type_string(m_type) %errors_cummulative;
|
||||
return (need_update)?false:true;
|
||||
}
|
||||
|
||||
@@ -1992,16 +2103,23 @@ void PresetCollection::update_after_user_presets_loaded()
|
||||
//BBS: validate_preset
|
||||
bool PresetCollection::validate_preset(const std::string &preset_name, std::string &inherit_name)
|
||||
{
|
||||
std::deque<Preset>::iterator it = this->find_preset_internal(preset_name);
|
||||
bool found = (it != m_presets.end()) && (it->name == preset_name) && (it->is_system || it->is_default);
|
||||
// Presets that came from system vendors, the built-in defaults, or any loaded bundle (local or
|
||||
// subscribed) are trusted — their g-code isn't user-authored, so the 3MF importer should not
|
||||
// warn about them.
|
||||
auto is_trusted = [](const Preset &p) { return p.is_system || p.is_default || p.is_from_bundle(); };
|
||||
|
||||
const std::string canonical_name = this->canonical_preset_name(preset_name);
|
||||
std::deque<Preset>::iterator it = this->find_preset_internal(canonical_name);
|
||||
bool found = (it != m_presets.end()) && (it->name == canonical_name) && is_trusted(*it);
|
||||
if (!found) {
|
||||
it = this->find_preset_renamed(preset_name);
|
||||
found = it != m_presets.end() && (it->is_system || it->is_default);
|
||||
it = this->find_preset_renamed(canonical_name);
|
||||
found = it != m_presets.end() && is_trusted(*it);
|
||||
}
|
||||
if (!found) {
|
||||
if (!inherit_name.empty()) {
|
||||
it = this->find_preset_internal(inherit_name);
|
||||
found = it != m_presets.end() && it->name == inherit_name && (it->is_system || it->is_default);
|
||||
const std::string canonical_inherit_name = this->canonical_preset_name(inherit_name);
|
||||
it = this->find_preset_internal(canonical_inherit_name);
|
||||
found = it != m_presets.end() && it->name == canonical_inherit_name && is_trusted(*it);
|
||||
if (found)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name;
|
||||
else
|
||||
@@ -2078,7 +2196,7 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
cfg.apply_only(combined_config, keys, true);
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
|
||||
//BBS: add different settings check logic, replace the old system preset's default value with new system preset's default values
|
||||
//add different settings check logic, replace the old system preset's default value with new system preset's default values
|
||||
std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
|
||||
bool found = it != m_presets.end() && it->name == original_name;
|
||||
if (! found) {
|
||||
@@ -2440,7 +2558,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
// Preset with the same name found.
|
||||
Preset &preset = *it;
|
||||
//BBS: add project embedded preset logic
|
||||
if (preset.is_default || preset.is_system) {
|
||||
if (!preset.can_overwrite()) {
|
||||
//if (preset.is_default || preset.is_external || preset.is_system)
|
||||
// Cannot overwrite the default preset.
|
||||
//BBS: add lock logic for sync preset in background
|
||||
@@ -2499,6 +2617,9 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
preset.is_default = false;
|
||||
preset.is_system = false;
|
||||
preset.is_external = false;
|
||||
|
||||
preset.bundle_id.clear();
|
||||
|
||||
preset.file = this->path_for_preset(preset);
|
||||
// The newly saved preset will be activated -> make it visible.
|
||||
preset.is_visible = true;
|
||||
@@ -2541,7 +2662,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
bool PresetCollection::delete_current_preset()
|
||||
{
|
||||
Preset &selected = this->get_selected_preset();
|
||||
if (selected.is_default)
|
||||
if (!selected.can_overwrite())
|
||||
return false;
|
||||
|
||||
if (get_preset_base(selected) == &selected) {
|
||||
@@ -2572,18 +2693,24 @@ bool PresetCollection::delete_current_preset()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PresetCollection::delete_preset(const std::string& name)
|
||||
bool PresetCollection::delete_preset(const std::string& name, bool force)
|
||||
{
|
||||
auto it = this->find_preset_internal(name);
|
||||
Preset *preset_ptr = this->find_preset(name, false, true);
|
||||
if (preset_ptr == nullptr)
|
||||
return false;
|
||||
|
||||
auto it = this->find_preset_internal(preset_ptr->name);
|
||||
if (it == m_presets.end() || it->name != preset_ptr->name)
|
||||
return false;
|
||||
|
||||
Preset& preset = *it;
|
||||
if (preset.is_default)
|
||||
// ORCA: if the preset can't be overridden then don't allow deletion
|
||||
// force=true bypasses this for bundle preset cleanup from cloud sync
|
||||
if (!force && !preset.can_overwrite())
|
||||
return false;
|
||||
//BBS: add project embedded preset logic and refine is_external
|
||||
//if (!preset.is_external && !preset.is_system) {
|
||||
if (! preset.is_system) {
|
||||
preset.remove_files();
|
||||
}
|
||||
|
||||
preset.remove_files();
|
||||
|
||||
//BBS: add lock logic for sync preset in background
|
||||
lock();
|
||||
set_printer_hold_alias(it->alias, *it, true);
|
||||
@@ -2593,6 +2720,30 @@ bool PresetCollection::delete_preset(const std::string& name)
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetCollection::check_and_fix_syncinfo(Preset& preset, const std::string& user_id)
|
||||
{
|
||||
// user id can't be empty
|
||||
if (user_id.empty())
|
||||
return;
|
||||
// correct the sync info if preset.user_id is empty(the profile json file is copied to the user folder with missing .info file) or preset.user_id
|
||||
// is not equal to the current user id or preset.setting_id is not in expected format(the .info is copied from the older format)
|
||||
if (preset.user_id.empty() || preset.user_id != user_id || preset.setting_id.find('-') == std::string::npos) {
|
||||
preset.user_id = user_id;
|
||||
preset.setting_id = "";
|
||||
|
||||
if (preset.base_id.empty()) {
|
||||
const std::string inherits = Preset::inherits(preset.config);
|
||||
Preset* parent_preset = find_preset2(inherits, true);
|
||||
if (parent_preset)
|
||||
preset.base_id = parent_preset->setting_id;
|
||||
}
|
||||
// tell the sync logic to sync it as a new preset
|
||||
preset.updated_time = 0;
|
||||
preset.sync_info = "create";
|
||||
preset.save_info();
|
||||
}
|
||||
}
|
||||
|
||||
const Preset* PresetCollection::get_selected_preset_parent() const
|
||||
{
|
||||
if (this->get_selected_idx() == size_t(-1))
|
||||
@@ -2655,7 +2806,7 @@ const Preset *PresetCollection::get_preset_base(const Preset &child) const
|
||||
// Handle user preset
|
||||
if (child.inherits().empty())
|
||||
return &child; // this is user root
|
||||
auto inherits = find_preset2(child.inherits(),true);
|
||||
auto inherits = find_preset2(child.inherits(), true);
|
||||
return inherits ? get_preset_base(*inherits) : nullptr;
|
||||
}
|
||||
|
||||
@@ -2729,20 +2880,21 @@ const std::string& PresetCollection::get_suffix_modified() {
|
||||
// If a preset was not found by its name, null is returned.
|
||||
Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found, bool real, bool only_from_library)
|
||||
{
|
||||
Preset key(m_type, name, false);
|
||||
auto it = this->find_preset_internal(name, only_from_library);
|
||||
// Ensure that a temporary copy is returned if the preset found is currently selected.
|
||||
return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin(), real) :
|
||||
first_visible_if_not_found ? &this->first_visible() : nullptr;
|
||||
const ParsedName parsed = parse_preset_name(name);
|
||||
const std::string canonical = get_preset_canonical_name(parsed.bare, PresetOrigin(parsed.kind, parsed.bundle_id));
|
||||
auto it = this->find_preset_internal(canonical, only_from_library);
|
||||
if (it != m_presets.end() && it->name == canonical)
|
||||
return &this->preset(it - m_presets.begin(), real);
|
||||
return first_visible_if_not_found ? &this->first_visible() : nullptr;
|
||||
}
|
||||
|
||||
Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/* = true */)
|
||||
{
|
||||
auto preset = find_preset(name,false,true);
|
||||
auto preset = find_preset(name, false, true);
|
||||
if (preset == nullptr) {
|
||||
auto _name = get_preset_name_renamed(name);
|
||||
if (_name != nullptr)
|
||||
preset = find_preset(*_name,false,true);
|
||||
preset = find_preset(*_name, false, true);
|
||||
if (auto_match && preset == nullptr) {
|
||||
//Orca: one more try, find the most likely preset in OrcaFilamentLibrary
|
||||
if (name.find("Generic") != std::string::npos) {
|
||||
@@ -2758,7 +2910,6 @@ Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
@@ -3110,10 +3261,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force;
|
||||
std::string name = Preset::remove_suffix_modified(name_w_suffix);
|
||||
const std::string normalized_name = this->canonical_preset_name(name);
|
||||
// 1) Try to find the preset by its name.
|
||||
auto it = this->find_preset_internal(name);
|
||||
auto it = this->find_preset_internal(normalized_name);
|
||||
size_t idx = 0;
|
||||
if (it != m_presets.end() && it->name == name && it->is_visible)
|
||||
if (it != m_presets.end() && it->name == normalized_name && it->is_visible)
|
||||
// Preset found by its name and it is visible.
|
||||
idx = it - m_presets.begin();
|
||||
else {
|
||||
@@ -3146,11 +3298,12 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
|
||||
{
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name;
|
||||
const std::string canonical_name = this->canonical_preset_name(name);
|
||||
// 1) Try to find the preset by its name.
|
||||
auto it = this->find_preset_internal(name);
|
||||
auto it = this->find_preset_internal(canonical_name);
|
||||
|
||||
size_t idx = (size_t)-1;
|
||||
if (it != m_presets.end() && it->name == name && it->is_visible)
|
||||
if (it != m_presets.end() && it->name == canonical_name && it->is_visible)
|
||||
// Preset found by its name.
|
||||
idx = it - m_presets.begin();
|
||||
// 2) Select the new preset.
|
||||
@@ -3262,9 +3415,13 @@ void PresetCollection::update_map_system_profile_renamed()
|
||||
|
||||
void PresetCollection::set_custom_preset_alias(Preset &preset)
|
||||
{
|
||||
// For filaments, remove the postfix
|
||||
// For printers, there is nothing to remove
|
||||
// For prints AKA processes, the postfix should be kept
|
||||
// Alias should be set here, as the preset name may be augmented further later (i.e., prefixing relative path for bundles)
|
||||
std::string alias_name;
|
||||
std::string preset_name = get_preset_bare_name(preset.name);
|
||||
if (m_type == Preset::Type::TYPE_FILAMENT && preset.config.has(BBL_JSON_KEY_INHERITS) && preset.config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS)->value.empty()) {
|
||||
std::string alias_name;
|
||||
std::string preset_name = preset.name;
|
||||
if (alias_name.empty()) {
|
||||
size_t end_pos = preset_name.find_first_of("@");
|
||||
if (end_pos != std::string::npos) {
|
||||
@@ -3272,14 +3429,14 @@ void PresetCollection::set_custom_preset_alias(Preset &preset)
|
||||
boost::trim_right(alias_name);
|
||||
}
|
||||
}
|
||||
if (alias_name.empty() || is_alias_exist(alias_name, &preset))
|
||||
preset.alias = "";
|
||||
else {
|
||||
preset.alias = std::move(alias_name);
|
||||
m_map_alias_to_profile_name[preset.alias].push_back(preset.name);
|
||||
set_printer_hold_alias(preset.alias, preset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
alias_name = preset_name;
|
||||
}
|
||||
|
||||
preset.alias = std::move(alias_name);
|
||||
m_map_alias_to_profile_name[preset.alias].push_back(preset.name);
|
||||
set_printer_hold_alias(preset.alias, preset);
|
||||
}
|
||||
|
||||
void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove)
|
||||
@@ -3380,7 +3537,7 @@ std::string PresetCollection::path_from_name(const std::string &new_name, bool d
|
||||
|
||||
std::string PresetCollection::path_for_preset(const Preset &preset) const
|
||||
{
|
||||
return path_from_name(preset.name, is_base_preset(preset));
|
||||
return path_from_name(get_preset_bare_name(preset.name), is_base_preset(preset));
|
||||
}
|
||||
|
||||
const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
#define PRESET_TEMPLATE_DIR "Template"
|
||||
#define PRESET_CUSTOM_VENDOR "Custom"
|
||||
|
||||
// Orca: bundle import directories
|
||||
#define PRESET_LOCAL_DIR "_local"
|
||||
#define PRESET_SUBSCRIBED_DIR "_subscribed"
|
||||
#define PRESET_BUNDLE_METADATA "bundle_metadata.json"
|
||||
|
||||
//BBS: iot preset type strings
|
||||
#define PRESET_IOT_PRINTER_TYPE "printer"
|
||||
#define PRESET_IOT_FILAMENT_TYPE "filament"
|
||||
@@ -228,7 +233,8 @@ public:
|
||||
//BBS: add type for project-embedded
|
||||
bool is_project_embedded = false;
|
||||
ConfigSubstitutions *loading_substitutions{nullptr};
|
||||
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded; }
|
||||
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded && ! this->is_from_bundle(); }
|
||||
bool can_overwrite() const { return ! this->is_default && ! this->is_system && ! this->is_from_bundle(); }
|
||||
//bool is_user() const { return ! this->is_default && ! this->is_system; }
|
||||
|
||||
// Name of the preset, usually derived form the file name.
|
||||
@@ -261,6 +267,11 @@ public:
|
||||
// Orca: flag to indicate if this preset is from Orca Filament Library
|
||||
bool m_from_orca_filament_lib = false;
|
||||
|
||||
// Orca: bundle tracking - imported preset bundles. Bundle ID: UUID (OrcaCloud) or name+timestamp (external).
|
||||
// Presence of bundle_id is the source of truth for "came from a bundle".
|
||||
std::string bundle_id;
|
||||
bool is_from_bundle() const { return ! bundle_id.empty(); }
|
||||
|
||||
//BBS
|
||||
Semver version; // version of preset
|
||||
std::string ini_str; // ini string of preset
|
||||
@@ -279,7 +290,7 @@ public:
|
||||
static Preset::Type get_type_from_string(std::string type_str);
|
||||
void load_info(const std::string& file);
|
||||
void save_info(std::string file = "");
|
||||
void remove_files();
|
||||
void remove_files(bool cloud_already_deleted = false);
|
||||
|
||||
//BBS: add logic for only difference save
|
||||
//if parent_config is null, save all keys, otherwise, only save difference
|
||||
@@ -395,6 +406,28 @@ bool is_compatible_with_print (const PresetWithVendorProfile &preset, const Pre
|
||||
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
|
||||
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
|
||||
|
||||
// Where a preset is being loaded from. `Auto` lets load_presets() infer from the directory path.
|
||||
struct PresetOrigin {
|
||||
enum class Kind { Auto, User, LocalBundle, SubscribedBundle };
|
||||
|
||||
Kind kind { Kind::Auto };
|
||||
std::string bundle_id;
|
||||
|
||||
PresetOrigin() = default;
|
||||
PresetOrigin(Kind kind, std::string bundle_id = {}) : kind(kind), bundle_id(std::move(bundle_id)) {}
|
||||
|
||||
bool is_bundle() const { return kind == Kind::LocalBundle || kind == Kind::SubscribedBundle; }
|
||||
};
|
||||
|
||||
// Prepend the bundle folder to `preset_bare_name` based on `origin`. No-op for non-bundle origins.
|
||||
std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin);
|
||||
|
||||
// Tail segment of a canonical name — what's written to the bundle's .json filename and JSON "name" field.
|
||||
std::string get_preset_bare_name(const std::string &canonical_name);
|
||||
|
||||
// Resolve an origin from a directory path when the caller passes Kind::Auto.
|
||||
PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin = PresetOrigin());
|
||||
|
||||
enum class PresetSelectCompatibleType {
|
||||
// Never select a compatible preset if the newly selected profile is not compatible.
|
||||
Never,
|
||||
@@ -471,12 +504,12 @@ public:
|
||||
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, std::function<void(Preset&)> preset_loaded_fn = nullptr, const PresetOrigin &load_origin = PresetOrigin());
|
||||
|
||||
//BBS: update user presets directory
|
||||
void update_user_presets_directory(const std::string& dir_path, const std::string& type);
|
||||
void save_user_presets(const std::string& dir_path, const std::string& type, std::vector<std::string>& need_to_delete_list);
|
||||
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
|
||||
void save_user_presets(const std::string& dir_path, const std::string& type, std::map<std::string, std::string>& need_to_delete_list);
|
||||
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin = PresetOrigin(PresetOrigin::Kind::User));
|
||||
void update_after_user_presets_loaded();
|
||||
//BBS: get user presets
|
||||
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
|
||||
@@ -550,7 +583,11 @@ public:
|
||||
bool delete_current_preset();
|
||||
// Delete the current preset, activate the first visible preset.
|
||||
// returns true if the preset was deleted successfully.
|
||||
bool delete_preset(const std::string& name);
|
||||
// When force=true, bypasses the can_overwrite() check (used for bundle preset cleanup).
|
||||
bool delete_preset(const std::string& name, bool force = false);
|
||||
|
||||
// Verify and correct the sync metadata for the preset to ensure proper cloud synchronization.
|
||||
void check_and_fix_syncinfo(Preset& preset, const std::string& user_id);
|
||||
|
||||
// Enable / disable the "- default -" preset.
|
||||
void set_default_suppressed(bool default_suppressed);
|
||||
@@ -637,6 +674,7 @@ public:
|
||||
{
|
||||
return const_cast<PresetCollection*>(this)->find_preset2(name, auto_match);
|
||||
}
|
||||
|
||||
size_t first_visible_idx() const;
|
||||
// Return the index of the first visible, compatible, system base preset
|
||||
// matching the given filament_type. Falls back to base type, then any visible.
|
||||
@@ -774,6 +812,8 @@ protected:
|
||||
void set_custom_preset_alias(Preset &preset);
|
||||
|
||||
private:
|
||||
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
|
||||
|
||||
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
|
||||
static bool filament_preset_less(const Preset &a, const Preset &b) {
|
||||
bool a_generic = boost::starts_with(a.name, GENERIC_PREFIX);
|
||||
@@ -795,6 +835,7 @@ private:
|
||||
// The "-- default -- " preset is always the first, so it needs
|
||||
// to be handled differently.
|
||||
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
|
||||
// `name` must already be canonical — callers canonicalize via find_preset / canonical_preset_name.
|
||||
std::deque<Preset>::iterator find_preset_internal(const std::string &name, bool from_orca_lib_only = false)
|
||||
{
|
||||
auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(),
|
||||
@@ -864,7 +905,7 @@ private:
|
||||
friend class PresetBundle;
|
||||
|
||||
//BBS: mutex
|
||||
std::mutex m_mutex;
|
||||
std::recursive_mutex m_mutex;
|
||||
|
||||
// Orca: used for validation only
|
||||
int m_errors = 0;
|
||||
|
||||
+817
-33
@@ -1,4 +1,5 @@
|
||||
#include <cassert>
|
||||
#include <ctime>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
@@ -6,10 +7,10 @@
|
||||
#include "I18N.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "format.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <unordered_set>
|
||||
@@ -23,6 +24,8 @@
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <miniz/miniz.h>
|
||||
|
||||
// Mark string for localization and translate.
|
||||
@@ -235,6 +238,83 @@ DynamicPrintConfig PresetBundle::construct_full_config(
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string PresetBundle::find_preset_vendor(const std::string &preset_name, Preset::Type type)
|
||||
{
|
||||
// Get the resources preset directory (contains all bundled vendor profiles)
|
||||
fs::path system_dir = fs::path(Slic3r::resources_dir()) / PRESET_PROFILES_DIR;
|
||||
if (!fs::exists(system_dir) || !fs::is_directory(system_dir)) {
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " Resources profiles directory does not exist: " << system_dir.string();
|
||||
return "";
|
||||
}
|
||||
|
||||
// Determine which preset list key to search for based on type
|
||||
const char* preset_list_key = nullptr;
|
||||
if (type == Preset::Type::TYPE_PRINT)
|
||||
preset_list_key = BBL_JSON_KEY_PROCESS_LIST;
|
||||
else if (type == Preset::Type::TYPE_FILAMENT)
|
||||
preset_list_key = BBL_JSON_KEY_FILAMENT_LIST;
|
||||
else if (type == Preset::Type::TYPE_PRINTER)
|
||||
preset_list_key = BBL_JSON_KEY_MACHINE_LIST;
|
||||
else {
|
||||
// Not supported for other types
|
||||
return "";
|
||||
}
|
||||
|
||||
// Iterate through vendor JSON files in the system directory
|
||||
for (auto& dir_entry : fs::directory_iterator(system_dir)) {
|
||||
std::string vendor_file = dir_entry.path().string();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Checking vendor: " << vendor_file;
|
||||
if (!Slic3r::is_json_file(vendor_file))
|
||||
continue;
|
||||
|
||||
// Get vendor name (filename without .json extension)
|
||||
std::string vendor_name = dir_entry.path().filename().string();
|
||||
vendor_name.erase(vendor_name.size() - 5); // Remove ".json"
|
||||
|
||||
try {
|
||||
// Load and parse the vendor JSON file
|
||||
boost::nowide::ifstream ifs(vendor_file);
|
||||
json j;
|
||||
ifs >> j;
|
||||
|
||||
// Check if the preset list key exists
|
||||
if (!j.contains(preset_list_key))
|
||||
continue;
|
||||
|
||||
auto& preset_list = j[preset_list_key];
|
||||
if (!preset_list.is_array())
|
||||
continue;
|
||||
|
||||
// Search for the preset in the list
|
||||
for (auto& preset_entry : preset_list) {
|
||||
if (!preset_entry.is_object())
|
||||
continue;
|
||||
|
||||
// Get the preset name
|
||||
std::string p_name;
|
||||
if (preset_entry.contains(BBL_JSON_KEY_NAME) && preset_entry[BBL_JSON_KEY_NAME].is_string())
|
||||
p_name = preset_entry[BBL_JSON_KEY_NAME].get<std::string>();
|
||||
|
||||
if (p_name != preset_name)
|
||||
continue;
|
||||
|
||||
// Found the preset! Get the vendor name and install the entire bundle
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Found preset " << p_name
|
||||
<< " in vendor bundle " << vendor_name;
|
||||
|
||||
return vendor_name;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to find vendor name for " << preset_name << ": " << e.what();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Could not find vendor for preset " << preset_name;
|
||||
return "";
|
||||
}
|
||||
|
||||
PresetBundle::PresetBundle()
|
||||
: prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()))
|
||||
, filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()), "Default Filament")
|
||||
@@ -848,6 +928,87 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
fs::path folder(user_folder / user);
|
||||
if (!fs::exists(folder)) fs::create_directory(folder);
|
||||
|
||||
bundles.WriteLock();
|
||||
bundles.m_bundles.clear();
|
||||
bundles.WriteUnlock();
|
||||
|
||||
// Load bundle metadata from _local directory first
|
||||
fs::path local_dir(folder / PRESET_LOCAL_DIR);
|
||||
if (fs::exists(local_dir)) {
|
||||
dir_user_presets_local = local_dir;
|
||||
for (auto& entry : fs::directory_iterator(local_dir)) {
|
||||
if (!fs::is_directory(entry.path())) continue;
|
||||
|
||||
std::string bundle_dir = entry.path().string();
|
||||
|
||||
fs::path metadata_file = entry.path() / PRESET_BUNDLE_METADATA;
|
||||
if (!fs::exists(metadata_file)) continue;
|
||||
|
||||
BundleMetadata metadata;
|
||||
if (!metadata.load_from_json(metadata_file.string())) continue;
|
||||
metadata.print_presets.clear();
|
||||
metadata.filament_presets.clear();
|
||||
metadata.printer_presets.clear();
|
||||
|
||||
// Add the profiles
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.filament_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.printer_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
metadata.bundle_type = BundleType::Local;
|
||||
metadata.path = metadata_file.string();
|
||||
|
||||
bundles.WriteLock();
|
||||
bundles.m_bundles[metadata.id] = metadata;
|
||||
bundles.WriteUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Load bundle metadata from _subscribed directory
|
||||
fs::path subscribed_dir(folder / PRESET_SUBSCRIBED_DIR);
|
||||
if (fs::exists(subscribed_dir)) {
|
||||
for (auto& entry : fs::directory_iterator(subscribed_dir)) {
|
||||
if (!fs::is_directory(entry.path())) continue;
|
||||
|
||||
std::string bundle_dir = entry.path().string();
|
||||
|
||||
fs::path metadata_file = entry.path() / PRESET_BUNDLE_METADATA;
|
||||
if (!fs::exists(metadata_file)) continue;
|
||||
|
||||
BundleMetadata metadata;
|
||||
if (!metadata.load_from_json(metadata_file.string())) continue;
|
||||
metadata.print_presets.clear();
|
||||
metadata.filament_presets.clear();
|
||||
metadata.printer_presets.clear();
|
||||
metadata.is_subscribed = true;
|
||||
|
||||
// Load presets from bundle (same logic as __local__)
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.filament_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.printer_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
|
||||
metadata.bundle_type = BundleType::Subscribed;
|
||||
metadata.path = metadata_file.string();
|
||||
metadata.update_available = false;
|
||||
|
||||
bundles.WriteLock();
|
||||
bundles.m_bundles[metadata.id] = metadata;
|
||||
bundles.WriteUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// BBS do not load sla_print
|
||||
// BBS: change directoties by design
|
||||
try {
|
||||
@@ -917,15 +1078,15 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &
|
||||
PresetCollection *preset_collection = nullptr;
|
||||
if (type_iter->second == PRESET_IOT_PRINT_TYPE) {
|
||||
preset_collection = &(this->prints);
|
||||
process_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule);
|
||||
process_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule, PresetOrigin(PresetOrigin::Kind::User));
|
||||
}
|
||||
else if (type_iter->second == PRESET_IOT_FILAMENT_TYPE) {
|
||||
preset_collection = &(this->filaments);
|
||||
filament_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule);
|
||||
filament_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule, PresetOrigin(PresetOrigin::Kind::User));
|
||||
}
|
||||
else if (type_iter->second == PRESET_IOT_PRINTER_TYPE) {
|
||||
preset_collection = &(this->printers);
|
||||
machine_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule);
|
||||
machine_added |= preset_collection->load_user_preset(name, value_map, substitutions, substitution_rule, PresetOrigin(PresetOrigin::Kind::User));
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid type %1% for setting %2%") %type_iter->second %name;
|
||||
@@ -959,32 +1120,193 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
bool PresetBundle::apply_vendor_config(
|
||||
const std::map<std::string, std::map<std::string, std::set<std::string>>>& new_vendors,
|
||||
const std::map<std::string, std::string>& new_filaments,
|
||||
AppConfig* app_config,
|
||||
bool overwrite,
|
||||
const std::string& preferred_printer_model,
|
||||
const std::string& preferred_printer_variant,
|
||||
const std::string& preferred_filament)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Get current configuration from AppConfig
|
||||
const auto old_vendors = app_config->vendors();
|
||||
const auto old_filaments = app_config->has_section(AppConfig::SECTION_FILAMENTS)
|
||||
? app_config->get_section(AppConfig::SECTION_FILAMENTS)
|
||||
: std::map<std::string, std::string>();
|
||||
|
||||
// Find vendors that need installation
|
||||
const auto vendor_dir = (fs::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||
|
||||
std::vector<std::string> install_bundles;
|
||||
for (const auto &it : new_vendors) {
|
||||
if (it.second.size() > 0) {
|
||||
auto vendor_file = vendor_dir / (it.first + ".json");
|
||||
if (!fs::exists(vendor_file)) {
|
||||
install_bundles.emplace_back(it.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install bundles from resources
|
||||
if (!install_bundles.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Installing " << install_bundles.size() << " vendor bundles from resources";
|
||||
if (!Slic3r::install_vendor_bundles_from_resources(install_bundles)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to install vendor bundles";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resource directory";
|
||||
}
|
||||
|
||||
// For each @System filament, check if a vendor-specific override exists
|
||||
// in the loaded profiles. If so, replace the @System variant with the
|
||||
// override (e.g. replace "Generic ABS @System" with BBL "Generic ABS").
|
||||
// When printers from the default bundle are also selected, keep @System
|
||||
// too since those printers need it.
|
||||
static const std::string system_suffix = " @System";
|
||||
auto it_default = new_vendors.find(PresetBundle::ORCA_DEFAULT_BUNDLE);
|
||||
bool has_default_bundle_printer = it_default != new_vendors.end() && !it_default->second.empty();
|
||||
|
||||
// Check if any non-default vendor has selected printers
|
||||
bool has_vendor_printer = false;
|
||||
for (const auto& [vendor, models] : new_vendors) {
|
||||
if (vendor != PresetBundle::ORCA_DEFAULT_BUNDLE && !models.empty()) {
|
||||
has_vendor_printer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> supplemented_filaments;
|
||||
for (const auto& [name, value] : new_filaments) {
|
||||
if (name.size() > system_suffix.size() &&
|
||||
name.compare(name.size() - system_suffix.size(), system_suffix.size(), system_suffix) == 0) {
|
||||
std::string short_name = name.substr(0, name.size() - system_suffix.size());
|
||||
|
||||
if (has_vendor_printer) {
|
||||
// Check if this filament exists in the loaded vendor profiles
|
||||
// For @System filaments, we check if the short_name exists as a vendor-specific filament
|
||||
bool has_vendor_filament = false;
|
||||
for (const auto& [vendor, models] : new_vendors) {
|
||||
if (vendor != PresetBundle::ORCA_DEFAULT_BUNDLE) {
|
||||
auto vendor_it = this->vendors.find(vendor);
|
||||
// Check if this vendor is loaded in the preset bundle
|
||||
if (vendor_it != this->vendors.end()) {
|
||||
// Vendor is loaded, check if the filament exists
|
||||
for (auto f : vendor_it->second.default_filaments) {
|
||||
BOOST_LOG_TRIVIAL(info) << " checking if vendor filament " << f << " matches " << short_name << "(" << name << ")";
|
||||
if (f.find(short_name) != std::string::npos) {
|
||||
BOOST_LOG_TRIVIAL(info) << name << " has filament from vendor: " << vendor;
|
||||
has_vendor_filament = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_vendor_filament) {
|
||||
supplemented_filaments[short_name] = value;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Replacing @System filament: '" << name << "' -> '" << short_name << "'";
|
||||
if (has_default_bundle_printer) {
|
||||
supplemented_filaments[name] = value;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Also keeping '" << name << "' for default bundle printers";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
supplemented_filaments[name] = value;
|
||||
}
|
||||
|
||||
// Update AppConfig - merge with existing values instead of overwriting depending on bool
|
||||
if (overwrite) {
|
||||
app_config->set_section(AppConfig::SECTION_FILAMENTS, supplemented_filaments);
|
||||
app_config->set_vendors(new_vendors);
|
||||
}
|
||||
else {
|
||||
// Merge filaments
|
||||
std::map<std::string, std::string> merged_filaments = old_filaments;
|
||||
for (const auto& [name, value] : supplemented_filaments) {
|
||||
merged_filaments[name] = value;
|
||||
}
|
||||
app_config->set_section(AppConfig::SECTION_FILAMENTS, merged_filaments);
|
||||
|
||||
// Merge vendors
|
||||
std::map<std::string, std::map<std::string, std::set<std::string>>> merged_vendors = old_vendors;
|
||||
for (const auto& [vendor, models] : new_vendors) {
|
||||
auto& vendor_entry = merged_vendors[vendor];
|
||||
for (const auto& [model, variants] : models) {
|
||||
auto& model_entry = vendor_entry[model];
|
||||
model_entry.insert(variants.begin(), variants.end());
|
||||
}
|
||||
}
|
||||
app_config->set_vendors(merged_vendors);
|
||||
}
|
||||
|
||||
// Load presets with new configuration
|
||||
this->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable,
|
||||
{preferred_printer_model, preferred_printer_variant, preferred_filament, std::string()});
|
||||
|
||||
// Ensure active filament compatibility
|
||||
// If the active filament is not in the wizard-selected filaments, switch to the first
|
||||
// compatible wizard-selected filament. This handles the first-run case where load_presets
|
||||
// falls back to "Generic PLA" even though the user selected a different filament.
|
||||
if (!supplemented_filaments.empty()) {
|
||||
bool active_filament_selected = supplemented_filaments.count(this->filament_presets.front()) > 0;
|
||||
if (!active_filament_selected) {
|
||||
for (const auto& [filament_name, _] : supplemented_filaments) {
|
||||
const Preset* preset = this->filaments.find_preset(filament_name);
|
||||
if (preset && preset->is_visible && preset->is_compatible) {
|
||||
this->filaments.select_preset_by_name(filament_name, true);
|
||||
this->filament_presets.front() = this->filaments.get_selected_preset_name();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export selections
|
||||
this->export_selections(*app_config);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Import presets from UI control
|
||||
PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string> & files,
|
||||
std::function<int(std::string const &)> override_confirm,
|
||||
ForwardCompatibilitySubstitutionRule rule)
|
||||
ForwardCompatibilitySubstitutionRule rule,
|
||||
AppConfig& config)
|
||||
{
|
||||
bundles.PauseRead(); // Pause threads from reading
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " entry";
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
int overwrite = 0;
|
||||
std::vector<std::string> result;
|
||||
std::string user_id = config.get("preset_folder");
|
||||
if (user_id.empty())
|
||||
user_id = DEFAULT_USER_FOLDER_NAME;
|
||||
this->update_user_presets_directory(user_id);
|
||||
for (auto &file : files) {
|
||||
if (Slic3r::is_json_file(file)) {
|
||||
import_json_presets(substitutions, file, override_confirm, rule, overwrite, result);
|
||||
}
|
||||
// Determine if it is a preset bundle
|
||||
if (boost::iends_with(file, ".orca_printer") || boost::iends_with(file, ".orca_filament") || boost::iends_with(file, ".zip")) {
|
||||
if (boost::iends_with(file, ".orca_printer") || boost::iends_with(file, ".orca_bundle") || boost::iends_with(file, ".orca_filament") || boost::iends_with(file, ".zip")) {
|
||||
boost::system::error_code ec;
|
||||
// create user folder
|
||||
fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
if (!fs::exists(user_folder)) fs::create_directory(user_folder, ec);
|
||||
if (ec) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << ec.message();
|
||||
// create default folder
|
||||
fs::path default_folder(user_folder / DEFAULT_USER_FOLDER_NAME);
|
||||
if (!fs::exists(default_folder)) fs::create_directory(default_folder, ec);
|
||||
fs::path configs_folder(user_folder / user_id);
|
||||
if (!fs::exists(configs_folder)) fs::create_directory(configs_folder, ec);
|
||||
if (ec) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << ec.message();
|
||||
//create temp folder
|
||||
//std::string user_default_temp_dir = data_dir() + "/" + PRESET_USER_DIR + "/" + DEFAULT_USER_FOLDER_NAME + "/" + "temp";
|
||||
fs::path temp_folder(default_folder / "temp");
|
||||
fs::path temp_folder(configs_folder / "temp");
|
||||
std::string user_default_temp_dir = temp_folder.make_preferred().string();
|
||||
if (fs::exists(temp_folder)) fs::remove_all(temp_folder);
|
||||
fs::create_directory(temp_folder, ec);
|
||||
@@ -995,13 +1317,6 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
||||
mz_zip_zero_struct(&zip_archive);
|
||||
mz_bool status;
|
||||
|
||||
/*if (!open_zip_reader(&zip_archive, file)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Failed to initialize reader ZIP archive";
|
||||
return substitutions;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "Success to initialize reader ZIP archive";
|
||||
}*/
|
||||
|
||||
FILE *zipFile = boost::nowide::fopen(file.c_str(), "rb");
|
||||
status = mz_zip_reader_init_cfile(&zip_archive, zipFile, 0, MZ_ZIP_FLAG_CASE_SENSITIVE | MZ_ZIP_FLAG_IGNORE_PATH);
|
||||
if (MZ_FALSE == status) {
|
||||
@@ -1011,6 +1326,38 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Success to initialize reader ZIP archive";
|
||||
}
|
||||
|
||||
// First, extract bundle_structure.json to get the bundle_id
|
||||
// Track whether bundle_structure.json exists to determine routing
|
||||
bool has_bundle_structure = false;
|
||||
BundleMetadata metadata;
|
||||
fs::path metadata_path = temp_folder / BUNDLE_STRUCTURE_JSON_NAME;
|
||||
status = mz_zip_reader_extract_file_to_file(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, encode_path(metadata_path.string().c_str()).c_str(), MZ_ZIP_FLAG_CASE_SENSITIVE);
|
||||
if (status) {
|
||||
if (metadata.load_from_json(metadata_path.string())) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Found bundle_id: " << metadata.id << " from " << BUNDLE_STRUCTURE_JSON_NAME;
|
||||
has_bundle_structure = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_bundle_structure && metadata.id.empty()) {
|
||||
boost::uuids::uuid uuid = boost::uuids::random_generator()();
|
||||
metadata.id = to_string(uuid);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " bundle_id was empty, so generating a UUID: " << metadata.id;
|
||||
}
|
||||
|
||||
// Build bundle directory path based on whether bundle_structure.json was present
|
||||
fs::path bundle_base_dir;
|
||||
if (has_bundle_structure) {
|
||||
// Use the bundle ID from metadata when bundle_structure.json exists
|
||||
bundle_base_dir = user_folder / user_id / PRESET_LOCAL_DIR / metadata.id;
|
||||
if (!fs::exists(bundle_base_dir))
|
||||
fs::create_directories(bundle_base_dir, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to create bundle directory: " << bundle_base_dir.string() << " error: " << ec.message();
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " No bundle_structure.json found, importing presets into the user preset directory";
|
||||
}
|
||||
|
||||
// Extract Files
|
||||
int num_files = mz_zip_reader_get_num_files(&zip_archive);
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
@@ -1018,7 +1365,7 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
||||
status = mz_zip_reader_file_stat(&zip_archive, i, &file_stat);
|
||||
if (status) {
|
||||
std::string file_name = file_stat.m_filename;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Form zip file: " << file << ". Read file name: " << file_stat.m_filename;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " From zip file: " << file << ". Read file name: " << file_stat.m_filename;
|
||||
size_t index = file_name.find_last_of('/');
|
||||
if (std::string::npos != index) {
|
||||
file_name = file_name.substr(index + 1);
|
||||
@@ -1032,16 +1379,44 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
||||
if (MZ_FALSE == status) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Failed to open target file: " << target_file_path;
|
||||
} else {
|
||||
bool is_success = import_json_presets(substitutions, target_file_path, override_confirm, rule, overwrite, result);
|
||||
bool is_success = import_json_presets(substitutions, target_file_path, override_confirm, rule, overwrite, result,
|
||||
has_bundle_structure ? bundle_base_dir.string() : std::string());
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " import target file: " << target_file_path << " import result" << is_success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set imported_time to current time if not already set
|
||||
if (metadata.imported_time == 0) {
|
||||
metadata.imported_time = std::time(nullptr);
|
||||
}
|
||||
|
||||
// Only save bundle_metadata.json for bundles (when bundle_structure.json was present)
|
||||
if (has_bundle_structure) {
|
||||
// Save metadata to bundle_metadata.json
|
||||
fs::path metadata_save_path = bundle_base_dir / PRESET_BUNDLE_METADATA;
|
||||
if (metadata.save_to_json(metadata_save_path.string())) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Saved bundle metadata to: " << metadata_save_path.string();
|
||||
|
||||
metadata.bundle_type = BundleType::Local;
|
||||
metadata.path = metadata_save_path.string();
|
||||
// Store the bundle metadata in m_bundles for tracking
|
||||
|
||||
|
||||
bundles.WriteLock();
|
||||
bundles.m_bundles[metadata.id] = metadata;
|
||||
bundles.WriteUnlock();
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to save bundle metadata to: " << metadata_save_path.string();
|
||||
}
|
||||
}
|
||||
|
||||
fclose(zipFile);
|
||||
if (fs::exists(temp_folder)) fs::remove_all(temp_folder, ec);
|
||||
if (ec) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " remove directory failed: " << ec.message();
|
||||
}
|
||||
}
|
||||
bundles.UnpauseRead();
|
||||
files = result;
|
||||
return substitutions;
|
||||
}
|
||||
@@ -1051,7 +1426,8 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
|
||||
std::function<int(std::string const &)> override_confirm,
|
||||
ForwardCompatibilitySubstitutionRule rule,
|
||||
int & overwrite,
|
||||
std::vector<std::string> & result)
|
||||
std::vector<std::string> & result,
|
||||
const std::string & bundle_dir)
|
||||
{
|
||||
try {
|
||||
DynamicPrintConfig config;
|
||||
@@ -1065,27 +1441,37 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
|
||||
boost::optional<Semver> version = Semver::parse(version_str);
|
||||
if (!version) return false;
|
||||
|
||||
std::string type_subdir; // also note the type subdir for bundles
|
||||
PresetCollection *collection = nullptr;
|
||||
if (config.has("printer_settings_id"))
|
||||
if (config.has("printer_settings_id")) {
|
||||
collection = &printers;
|
||||
else if (config.has("print_settings_id"))
|
||||
type_subdir = PRESET_PRINTER_NAME;
|
||||
}
|
||||
else if (config.has("print_settings_id")) {
|
||||
collection = &prints;
|
||||
else if (config.has("filament_settings_id"))
|
||||
type_subdir = PRESET_PRINT_NAME;
|
||||
}
|
||||
else if (config.has("filament_settings_id")) {
|
||||
collection = &filaments;
|
||||
type_subdir = PRESET_FILAMENT_NAME;
|
||||
}
|
||||
if (collection == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset type is unknown, not loading: " << name;
|
||||
return false;
|
||||
}
|
||||
const PresetOrigin load_origin = detect_origin_from_path(boost::filesystem::path(bundle_dir));
|
||||
const std::string preset_name = get_preset_canonical_name(name, load_origin);
|
||||
|
||||
if (overwrite == 0) overwrite = 1;
|
||||
if (auto p = collection->find_preset(name, false)) {
|
||||
if (auto p = collection->find_preset(preset_name, false)) {
|
||||
if (p->is_default || p->is_system) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset already present and is system preset, not loading: " << name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset already present and is system preset, not loading: " << preset_name;
|
||||
return false;
|
||||
}
|
||||
if (overwrite != 2 && overwrite != 3) overwrite = override_confirm(name); //3: yes to all 2: no to all
|
||||
if (overwrite != 2 && overwrite != 3) overwrite = override_confirm(preset_name); //3: yes to all 2: no to all
|
||||
}
|
||||
if (overwrite == 0 || overwrite == 2) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset already present, not loading: " << name;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset already present, not loading: " << preset_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1096,7 +1482,7 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
|
||||
if (inherits_config) {
|
||||
ConfigOptionString *option_str = dynamic_cast<ConfigOptionString *>(inherits_config);
|
||||
inherits_value = option_str->value;
|
||||
inherit_preset = collection->find_preset2(inherits_value);
|
||||
inherit_preset = collection->find_preset2(inherits_value, true);
|
||||
}
|
||||
if (inherit_preset) {
|
||||
new_config = inherit_preset->config;
|
||||
@@ -1116,7 +1502,8 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
|
||||
extend_default_config_length(new_config, true, default_preset.config);
|
||||
}
|
||||
|
||||
Preset &preset = collection->load_preset(collection->path_from_name(name, inherit_preset == nullptr), name, std::move(new_config), false);
|
||||
Preset &preset = collection->load_preset(collection->path_from_name(name, inherit_preset == nullptr), preset_name, std::move(new_config), false);
|
||||
preset.bundle_id = load_origin.bundle_id;
|
||||
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
|
||||
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
|
||||
preset.is_external = true;
|
||||
@@ -1136,7 +1523,17 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
|
||||
if (!config_substitutions.empty())
|
||||
substitutions.push_back({name, collection->type(), PresetConfigSubstitutions::Source::UserFile, file, std::move(config_substitutions)});
|
||||
collection->set_custom_preset_alias(preset);
|
||||
preset.save(inherit_preset ? &inherit_preset->config : nullptr);
|
||||
|
||||
// If bundle_dir is provided, use it for the save operation
|
||||
if (!bundle_dir.empty()) {
|
||||
if (!save_preset_to_bundle_dir(preset, collection, load_origin.bundle_id, type_subdir, bundle_dir)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to save preset " << preset_name << " to bundle directory";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
preset.save(inherit_preset ? &inherit_preset->config : nullptr);
|
||||
}
|
||||
|
||||
result.push_back(file);
|
||||
} catch (const std::ifstream::failure &err) {
|
||||
++m_errors;
|
||||
@@ -1149,7 +1546,7 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
|
||||
}
|
||||
|
||||
//BBS save user preset to user_id preset folder
|
||||
void PresetBundle::save_user_presets(AppConfig& config, std::vector<std::string>& need_to_delete_list)
|
||||
void PresetBundle::save_user_presets(AppConfig& config, std::map<std::string, std::string>& need_to_delete_list)
|
||||
{
|
||||
std::string user_sub_folder = DEFAULT_USER_FOLDER_NAME;
|
||||
if (!config.get("preset_folder").empty())
|
||||
@@ -1173,6 +1570,311 @@ void PresetBundle::save_user_presets(AppConfig& config, std::vector<std::string>
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished");
|
||||
}
|
||||
|
||||
void PresetBundle::check_and_fix_user_presets_syncinfo(const std::string& user_id)
|
||||
{
|
||||
auto process_collection = [&user_id](PresetCollection& collection) {
|
||||
collection.lock();
|
||||
for (auto& preset : collection) {
|
||||
if (preset.is_user()) {
|
||||
collection.check_and_fix_syncinfo(preset, user_id);
|
||||
}
|
||||
}
|
||||
collection.unlock();
|
||||
};
|
||||
process_collection(this->prints);
|
||||
process_collection(this->filaments);
|
||||
process_collection(this->printers);
|
||||
}
|
||||
|
||||
//Orca: Import subscribed bundle presets (load and save to disk in one operation)
|
||||
PresetsConfigSubstitutions PresetBundle::update_subscribed_presets(
|
||||
AppConfig& config,
|
||||
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
|
||||
const BundleMetadata& remote_metadata,
|
||||
ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
{
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::string errors_cumulative;
|
||||
bool process_added = false, filament_added = false, machine_added = false;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " enter, substitution_rule " << substitution_rule << ", bundle_id: " << remote_metadata.id << ", preset count: " << bundle_presets.size();
|
||||
|
||||
BundleMetadata merged_metadata;
|
||||
auto existing_it = bundles.m_bundles.find(remote_metadata.id);
|
||||
if (existing_it != bundles.m_bundles.end()) {
|
||||
merged_metadata = existing_it->second;
|
||||
} else {
|
||||
merged_metadata.imported_time = std::time(nullptr);
|
||||
}
|
||||
|
||||
merged_metadata.id = remote_metadata.id;
|
||||
merged_metadata.name = remote_metadata.name;
|
||||
merged_metadata.version = remote_metadata.version;
|
||||
merged_metadata.description = remote_metadata.description;
|
||||
merged_metadata.author = remote_metadata.author;
|
||||
merged_metadata.updated_time = remote_metadata.updated_time;
|
||||
merged_metadata.bundle_type = BundleType::Subscribed;
|
||||
merged_metadata.is_subscribed = true;
|
||||
merged_metadata.update_available = false;
|
||||
merged_metadata.unauthorized = false;
|
||||
|
||||
const PresetOrigin subscribed_origin(PresetOrigin::Kind::SubscribedBundle, remote_metadata.id);
|
||||
|
||||
std::unordered_set<std::string> remote_prints;
|
||||
std::unordered_set<std::string> remote_filaments;
|
||||
std::unordered_set<std::string> remote_printers;
|
||||
|
||||
for (const auto& [preset_name, value_map] : bundle_presets) {
|
||||
auto type_iter = value_map.find(BBL_JSON_KEY_TYPE);
|
||||
if (type_iter == value_map.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " cannot find type for preset " << preset_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string subscribed_name = get_preset_canonical_name(preset_name, subscribed_origin);
|
||||
if (type_iter->second == PRESET_IOT_PRINT_TYPE)
|
||||
remote_prints.insert(subscribed_name);
|
||||
else if (type_iter->second == PRESET_IOT_FILAMENT_TYPE)
|
||||
remote_filaments.insert(subscribed_name);
|
||||
else if (type_iter->second == PRESET_IOT_PRINTER_TYPE)
|
||||
remote_printers.insert(subscribed_name);
|
||||
}
|
||||
|
||||
auto remove_obsolete_bundle_presets =
|
||||
[&](PresetCollection& collection, const std::unordered_set<std::string>& remote_names, const char* type_name) -> int {
|
||||
int removed_count = 0;
|
||||
std::vector<std::string> to_delete;
|
||||
|
||||
for (const Preset& preset : collection.get_presets()) {
|
||||
if (!preset.is_from_bundle() || preset.bundle_id != remote_metadata.id)
|
||||
continue;
|
||||
|
||||
if (remote_names.find(preset.name) == remote_names.end())
|
||||
to_delete.push_back(preset.name);
|
||||
}
|
||||
|
||||
for (const std::string& preset_name : to_delete) {
|
||||
if (collection.delete_preset(preset_name, true)) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << type_name << " preset '" << preset_name << "' no longer in remote bundle, deleted";
|
||||
++removed_count;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to delete obsolete " << type_name << " preset '" << preset_name << "'";
|
||||
}
|
||||
}
|
||||
|
||||
return removed_count;
|
||||
};
|
||||
|
||||
int total_removed = 0;
|
||||
total_removed += remove_obsolete_bundle_presets(this->prints, remote_prints, "print");
|
||||
total_removed += remove_obsolete_bundle_presets(this->filaments, remote_filaments, "filament");
|
||||
total_removed += remove_obsolete_bundle_presets(this->printers, remote_printers, "printer");
|
||||
|
||||
if (total_removed > 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": deleted " << total_removed << " obsolete presets from bundle " << remote_metadata.id;
|
||||
}
|
||||
|
||||
// Get current user ID for path construction
|
||||
std::string user_id = config.get("preset_folder");
|
||||
if (user_id.empty()) user_id = DEFAULT_USER_FOLDER_NAME;
|
||||
|
||||
// Create the subscribed directory base path
|
||||
boost::filesystem::path user_folder(Slic3r::data_dir() + "/" + PRESET_USER_DIR);
|
||||
boost::filesystem::path subscribed_base(user_folder / user_id / PRESET_SUBSCRIBED_DIR);
|
||||
|
||||
// Ensure subscribed directory exists
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::exists(subscribed_base))
|
||||
boost::filesystem::create_directories(subscribed_base, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to create subscribed directory: " << subscribed_base.string() << " error: " << ec.message();
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
dir_user_presets_subscribed = subscribed_base;
|
||||
|
||||
// Create bundle directory
|
||||
boost::filesystem::path bundle_dir(subscribed_base / remote_metadata.id);
|
||||
if (!boost::filesystem::exists(bundle_dir))
|
||||
boost::filesystem::create_directories(bundle_dir, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to create bundle directory: " << bundle_dir.string() << " error: " << ec.message();
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
merged_metadata.print_presets.clear();
|
||||
merged_metadata.filament_presets.clear();
|
||||
merged_metadata.printer_presets.clear();
|
||||
|
||||
// Load each preset from the bundle and save to disk
|
||||
for (const auto& preset_entry : bundle_presets) {
|
||||
const std::string& preset_name = preset_entry.first;
|
||||
const std::string subscribed_name = get_preset_canonical_name(preset_name, subscribed_origin);
|
||||
std::map<std::string, std::string> value_map = preset_entry.second; // Make a copy since we might modify it
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " importing preset: " << preset_name << " from bundle: " << remote_metadata.id;
|
||||
|
||||
// Get the type first
|
||||
auto type_iter = value_map.find(BBL_JSON_KEY_TYPE);
|
||||
if (type_iter == value_map.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " cannot find type for preset " << preset_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this preset inherits from another preset inside the same bundle, rewrite the
|
||||
// reference to the canonical (bundle-prefixed) name so the lookup matches the stored identity.
|
||||
auto inherits_iter = value_map.find(BBL_JSON_KEY_INHERITS);
|
||||
if (inherits_iter != value_map.end() && !inherits_iter->second.empty() && bundle_presets.find(inherits_iter->second) != bundle_presets.end())
|
||||
inherits_iter->second = get_preset_canonical_name(inherits_iter->second, subscribed_origin);
|
||||
|
||||
try {
|
||||
PresetCollection* preset_collection = nullptr;
|
||||
std::string type_subdir;
|
||||
bool preset_added = false;
|
||||
|
||||
if (type_iter->second == PRESET_IOT_PRINT_TYPE) {
|
||||
preset_collection = &(this->prints);
|
||||
type_subdir = PRESET_PRINT_NAME;
|
||||
preset_added = preset_collection->load_user_preset(preset_name, value_map, substitutions, substitution_rule, subscribed_origin);
|
||||
process_added |= preset_added;
|
||||
}
|
||||
else if (type_iter->second == PRESET_IOT_FILAMENT_TYPE) {
|
||||
preset_collection = &(this->filaments);
|
||||
type_subdir = PRESET_FILAMENT_NAME;
|
||||
preset_added = preset_collection->load_user_preset(preset_name, value_map, substitutions, substitution_rule, subscribed_origin);
|
||||
filament_added |= preset_added;
|
||||
}
|
||||
else if (type_iter->second == PRESET_IOT_PRINTER_TYPE) {
|
||||
preset_collection = &(this->printers);
|
||||
type_subdir = PRESET_PRINTER_NAME;
|
||||
preset_added = preset_collection->load_user_preset(preset_name, value_map, substitutions, substitution_rule, subscribed_origin);
|
||||
machine_added |= preset_added;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " invalid type " << type_iter->second << " for preset " << preset_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If preset was loaded/added, save it to the bundle directory.
|
||||
// Find the preset that was just loaded using its canonical (bundle-prefixed) name.
|
||||
Preset* preset = preset_collection->find_preset(subscribed_name, false, true);
|
||||
if (preset) {
|
||||
// Use helper function to save preset to bundle directory
|
||||
if (!save_preset_to_bundle_dir(*preset, preset_collection, remote_metadata.id, type_subdir, bundle_dir.string())) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to save preset " << preset_name << " to bundle directory";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type_iter->second == PRESET_IOT_PRINT_TYPE)
|
||||
merged_metadata.print_presets.push_back(preset->name);
|
||||
else if (type_iter->second == PRESET_IOT_FILAMENT_TYPE)
|
||||
merged_metadata.filament_presets.push_back(preset->name);
|
||||
else if (type_iter->second == PRESET_IOT_PRINTER_TYPE)
|
||||
merged_metadata.printer_presets.push_back(preset->name);
|
||||
}
|
||||
}
|
||||
catch (const std::runtime_error& err) {
|
||||
errors_cumulative += err.what();
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " error importing preset " << preset_name << ": " << err.what();
|
||||
}
|
||||
}
|
||||
|
||||
boost::filesystem::path metadata_save_path = bundle_dir / PRESET_BUNDLE_METADATA;
|
||||
merged_metadata.path = metadata_save_path.string();
|
||||
bundles.m_bundles[remote_metadata.id] = merged_metadata;
|
||||
|
||||
if (bundles.m_bundles[remote_metadata.id].save_to_json(metadata_save_path.string())) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " saved bundle metadata to: " << metadata_save_path.string();
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to save bundle metadata to: " << metadata_save_path.string();
|
||||
}
|
||||
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
set_calibrate_printer("");
|
||||
|
||||
if (!errors_cumulative.empty())
|
||||
throw Slic3r::RuntimeError(errors_cumulative);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " finished, process_added " << process_added << ", filament_added " << filament_added << ", machine_added " << machine_added;
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
// Helper function: save preset to bundle directory with common logic
|
||||
// This function extracts the common code used by both import_json_presets and import_subscribed_presets
|
||||
bool PresetBundle::save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
|
||||
const std::string& bundle_id, const std::string& type_subdir,
|
||||
const std::string& bundle_base_dir)
|
||||
{
|
||||
if (bundle_base_dir.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " bundle_base_dir is empty, cannot save preset " << preset.name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store original directory path
|
||||
std::string original_dir_path = collection->m_dir_path;
|
||||
|
||||
try {
|
||||
// Create bundle directory if it doesn't exist
|
||||
boost::filesystem::path bundle_dir(bundle_base_dir);
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::exists(bundle_dir))
|
||||
boost::filesystem::create_directories(bundle_dir, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to create bundle directory: " << bundle_dir.string() << " error: " << ec.message();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create bundle type directory
|
||||
boost::filesystem::path type_dir = bundle_dir / type_subdir;
|
||||
if (!boost::filesystem::exists(type_dir)) {
|
||||
boost::filesystem::create_directories(type_dir, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to create type directory: " << type_dir.string() << " error: " << ec.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
preset.bundle_id = bundle_id;
|
||||
|
||||
// Bundle preset names may include the subscribed/local prefix path.
|
||||
// Persist the file under the type directory using only the base preset name.
|
||||
const std::string preset_filename = boost::filesystem::path(preset.name).filename().string();
|
||||
const std::string file_name = boost::iends_with(preset_filename, ".json") ? preset_filename : (preset_filename + ".json");
|
||||
preset.file = (type_dir / file_name).make_preferred().string();
|
||||
|
||||
// Save with parent config if inherits from another preset
|
||||
std::string inherits = Preset::inherits(preset.config);
|
||||
if (inherits.empty()) {
|
||||
// Root preset - save full config
|
||||
preset.save(nullptr);
|
||||
} else {
|
||||
Preset* parent_preset = collection->find_preset2(inherits, true);
|
||||
if (!parent_preset) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " cannot find parent preset for " << preset.name << ", inherits " << inherits;
|
||||
} else {
|
||||
if (preset.base_id.empty())
|
||||
preset.base_id = parent_preset->setting_id;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " saved preset " << preset.name
|
||||
<< " filament_id: " << preset.filament_id
|
||||
<< " base_id: " << preset.base_id
|
||||
<< " bundle: " << bundle_id;
|
||||
preset.save(&(parent_preset->config));
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original directory path
|
||||
collection->m_dir_path = original_dir_path;
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception saving preset " << preset.name << ": " << e.what();
|
||||
collection->m_dir_path = original_dir_path;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//BBS: save user preset to user_id preset folder
|
||||
void PresetBundle::update_user_presets_directory(const std::string preset_folder)
|
||||
{
|
||||
@@ -1296,6 +1998,11 @@ int PresetBundle::validate_presets(const std::string &file_name, DynamicPrintCon
|
||||
std::string filament_preset = filament_preset_name[index];
|
||||
std::string filament_inherits = inherits_values[index+1];
|
||||
|
||||
// filament_preset_name is padded up to filament_count from filament_diameter. Unfilled
|
||||
// slots have no assigned preset, so there's nothing to validate or warn about.
|
||||
if (filament_preset.empty())
|
||||
continue;
|
||||
|
||||
validated = this->filaments.validate_preset(filament_preset, filament_inherits);
|
||||
if (!validated) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":file_name %1%, found the filament %2% preset not inherit from system") % file_name %(index+1);
|
||||
@@ -1350,7 +2057,7 @@ void PresetBundle::remove_users_preset(AppConfig &config, std::map<std::string,
|
||||
preset.setting_id.clear();
|
||||
return false;
|
||||
}
|
||||
preset.remove_files();
|
||||
preset.remove_files(true /* cloud_already_deleted */);
|
||||
return true;
|
||||
};
|
||||
std::string preset_folder_user_id = config.get("preset_folder");
|
||||
@@ -1674,6 +2381,7 @@ void PresetBundle::update_system_maps()
|
||||
this->sla_prints .update_map_alias_to_profile_name();
|
||||
this->filaments .update_map_alias_to_profile_name();
|
||||
this->sla_materials.update_map_alias_to_profile_name();
|
||||
this->printers .update_map_alias_to_profile_name();
|
||||
|
||||
this->filaments.update_library_profile_excluded_from();
|
||||
}
|
||||
@@ -1698,13 +2406,13 @@ void PresetBundle::load_installed_printers(const AppConfig &config)
|
||||
|
||||
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const
|
||||
{
|
||||
// there are not aliases for Printers profiles
|
||||
if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
|
||||
if (preset_type == Preset::TYPE_INVALID)
|
||||
return alias;
|
||||
|
||||
const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints :
|
||||
preset_type == Preset::TYPE_SLA_PRINT ? sla_prints :
|
||||
preset_type == Preset::TYPE_FILAMENT ? filaments :
|
||||
preset_type == Preset::TYPE_PRINTER ? printers :
|
||||
sla_materials;
|
||||
|
||||
return presets.get_preset_name_by_alias(alias);
|
||||
@@ -4638,4 +5346,80 @@ bool PresetBundle::has_errors() const
|
||||
return has_errors;
|
||||
}
|
||||
|
||||
// Orca: BundleMetadata method implementations
|
||||
bool BundleMetadata::load_from_json(const std::string& path)
|
||||
{
|
||||
try {
|
||||
boost::nowide::ifstream ifs(path);
|
||||
if (!ifs.good())
|
||||
return false;
|
||||
|
||||
json j;
|
||||
ifs >> j;
|
||||
|
||||
if (j.contains("id")) this->id = j["id"].get<std::string>();
|
||||
|
||||
if (j.contains("name")) this->name = j["name"].get<std::string>();
|
||||
else if (j.contains("bundle_id")) this->name = j["bundle_id"].get<std::string>(); // backwards compat w bundle_structure.json
|
||||
|
||||
if (j.contains("version")) this->version = j["version"].get<std::string>();
|
||||
|
||||
if (j.contains("description")) this->description = j["description"].get<std::string>();
|
||||
else if (j.contains("bundle_type")) this->description = j["bundle_type"].get<std::string>(); // backwards compat w bundle_structure.json
|
||||
|
||||
if (j.contains("author")) this->author = j["author"].get<std::string>();
|
||||
|
||||
if (j.contains("imported_time")) this->imported_time = j["imported_time"].get<long long>();
|
||||
|
||||
if (j.contains("updated_time")) this->updated_time = j["updated_time"].get<long long>();
|
||||
|
||||
if (j.contains("print_presets"))
|
||||
this->print_presets = j["print_presets"].get<std::vector<std::string>>();
|
||||
|
||||
if (j.contains("filament_presets"))
|
||||
this->filament_presets = j["filament_presets"].get<std::vector<std::string>>();
|
||||
|
||||
if (j.contains("printer_presets"))
|
||||
this->printer_presets = j["printer_presets"].get<std::vector<std::string>>();
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to load bundle metadata from " << path << ": " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BundleMetadata::save_to_json(const std::string& path) const
|
||||
{
|
||||
auto strip_prefix = [](const std::vector<std::string>& names) {
|
||||
json arr = json::array();
|
||||
for (const auto& name : names)
|
||||
{
|
||||
arr.push_back(boost::filesystem::path(name).filename().string());
|
||||
std::string test = boost::filesystem::path(name).filename().string();
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
try {
|
||||
json j;
|
||||
j["id"] = this->id;
|
||||
j["name"] = this->name;
|
||||
j["version"] = this->version;
|
||||
j["description"] = this->description;
|
||||
j["author"] = this->author;
|
||||
j["imported_time"] = this->imported_time;
|
||||
j["updated_time"] = this->updated_time;
|
||||
|
||||
j["print_presets"] = strip_prefix(this->print_presets);
|
||||
j["filament_presets"] = strip_prefix(this->filament_presets);
|
||||
j["printer_presets"] = strip_prefix(this->printer_presets);
|
||||
|
||||
boost::nowide::ofstream ofs(path);
|
||||
ofs << j.dump(4);
|
||||
return ofs.good();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to save bundle metadata to " << path << ": " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include "enum_bitmask.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <array>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
#define DEFAULT_USER_FOLDER_NAME "default"
|
||||
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
|
||||
@@ -72,6 +74,79 @@ struct FilamentBaseInfo
|
||||
int filament_printable = 3;
|
||||
};
|
||||
|
||||
enum BundleType{
|
||||
Default = 0,
|
||||
Local,
|
||||
Subscribed,
|
||||
};
|
||||
|
||||
// Orca: Bundle metadata structure for imported preset bundles
|
||||
struct BundleMetadata
|
||||
{
|
||||
std::string id; // Bundle ID: UUID (OrcaCloud) or name+timestamp (external)
|
||||
std::string name; // Display name
|
||||
std::string version; // Bundle version
|
||||
std::string description;
|
||||
std::string author;
|
||||
long long imported_time{0};
|
||||
long long updated_time{0};
|
||||
|
||||
BundleType bundle_type{Default};
|
||||
std::string path;
|
||||
|
||||
// Cached preset names by type (populated on load)
|
||||
std::vector<std::string> print_presets;
|
||||
std::vector<std::string> filament_presets;
|
||||
std::vector<std::string> printer_presets;
|
||||
|
||||
// Runtime-only flags
|
||||
bool is_subscribed{false};
|
||||
bool update_available{false};
|
||||
bool not_found{false};
|
||||
bool unauthorized{false};
|
||||
|
||||
bool load_from_json(const std::string& path);
|
||||
bool save_to_json(const std::string& path) const;
|
||||
};
|
||||
|
||||
struct PresetBundleMetadata
|
||||
{
|
||||
// To make sure write locks take precedent, pausereads needs to be true for when Orca needs to read or manipulate the container
|
||||
// We only need to explicitly pause reads when entering a region in Orca which we deem necessary to quickly acquire write locks.
|
||||
std::unordered_map<std::string, BundleMetadata> m_bundles;
|
||||
std::shared_mutex RWMtx;
|
||||
std::atomic<bool> pauseReads{false};
|
||||
|
||||
void PauseRead()
|
||||
{
|
||||
pauseReads.store(true);
|
||||
}
|
||||
|
||||
void UnpauseRead()
|
||||
{
|
||||
pauseReads.store(false);
|
||||
}
|
||||
|
||||
void ReadLock()
|
||||
{
|
||||
RWMtx.lock_shared();
|
||||
}
|
||||
void ReadUnlock()
|
||||
{
|
||||
RWMtx.unlock_shared();
|
||||
}
|
||||
|
||||
void WriteLock()
|
||||
{
|
||||
RWMtx.lock();
|
||||
}
|
||||
|
||||
void WriteUnlock()
|
||||
{
|
||||
RWMtx.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
// Bundle of Print + Filament + Printer presets.
|
||||
class PresetBundle
|
||||
{
|
||||
@@ -82,6 +157,10 @@ public:
|
||||
std::vector<Preset> &in_filament_presets,
|
||||
bool apply_extruder,
|
||||
std::optional<std::vector<int>> filament_maps_new);
|
||||
|
||||
// ORCA: utility function to find the vendor for a given preset name
|
||||
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
|
||||
|
||||
PresetBundle();
|
||||
PresetBundle(const PresetBundle &rhs);
|
||||
PresetBundle& operator=(const PresetBundle &rhs);
|
||||
@@ -114,19 +193,53 @@ public:
|
||||
// BBS Load user presets
|
||||
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule);
|
||||
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
|
||||
PresetsConfigSubstitutions import_presets(std::vector<std::string> &files, std::function<int(std::string const &)> override_confirm, ForwardCompatibilitySubstitutionRule rule);
|
||||
bool import_json_presets(PresetsConfigSubstitutions & substitutions,
|
||||
std::string & file,
|
||||
std::function<int(std::string const &)> override_confirm,
|
||||
ForwardCompatibilitySubstitutionRule rule,
|
||||
int & overwrite,
|
||||
std::vector<std::string> & result);
|
||||
void save_user_presets(AppConfig& config, std::vector<std::string>& need_to_delete_list);
|
||||
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
|
||||
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
|
||||
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
|
||||
const BundleMetadata& remote_metadata,
|
||||
ForwardCompatibilitySubstitutionRule rule);
|
||||
|
||||
PresetsConfigSubstitutions import_presets(std::vector<std::string>& files,
|
||||
std::function<int(std::string const&)> override_confirm,
|
||||
ForwardCompatibilitySubstitutionRule rule,
|
||||
AppConfig& config);
|
||||
|
||||
bool import_json_presets(PresetsConfigSubstitutions& substitutions,
|
||||
std::string& file,
|
||||
std::function<int(std::string const&)> override_confirm,
|
||||
ForwardCompatibilitySubstitutionRule rule,
|
||||
int& overwrite,
|
||||
std::vector<std::string>& result,
|
||||
const std::string& bundle_dir = "");
|
||||
|
||||
void save_user_presets(AppConfig& config, std::map<std::string, std::string>& need_to_delete_list);
|
||||
void check_and_fix_user_presets_syncinfo(const std::string& user_id);
|
||||
void remove_users_preset(AppConfig &config, std::map<std::string, std::map<std::string, std::string>> * my_presets = nullptr);
|
||||
void update_user_presets_directory(const std::string preset_folder);
|
||||
void remove_user_presets_directory(const std::string preset_folder);
|
||||
void update_system_preset_setting_ids(std::map<std::string, std::map<std::string, std::string>>& system_presets);
|
||||
|
||||
// Apply vendor configuration changes (Core library version, no GUI dependencies)
|
||||
// This function installs vendors from resources and loads them into the preset bundle
|
||||
//
|
||||
// Parameters:
|
||||
// new_vendors: Map of vendor names to their enabled printer models and variants
|
||||
// new_filaments: Map of filament names to their settings
|
||||
// app_config: Pointer to AppConfig to update with new vendor/filament selections
|
||||
// preferred_printer_model: Optional preferred printer model to select
|
||||
// preferred_printer_variant: Optional preferred printer variant to select
|
||||
// preferred_filament: Optional preferred filament to select
|
||||
//
|
||||
// Returns: true if successful, false otherwise
|
||||
bool apply_vendor_config(
|
||||
const std::map<std::string, std::map<std::string, std::set<std::string>>>& new_vendors,
|
||||
const std::map<std::string, std::string>& new_filaments,
|
||||
AppConfig* app_config,
|
||||
bool overwrite = true,
|
||||
const std::string& preferred_printer_model = std::string(),
|
||||
const std::string& preferred_printer_variant = std::string(),
|
||||
const std::string& preferred_filament = std::string());
|
||||
|
||||
//BBS: add API to get previous machine
|
||||
int validate_presets(const std::string &file_name, DynamicPrintConfig& config, std::set<std::string>& different_gcodes);
|
||||
|
||||
@@ -232,6 +345,12 @@ public:
|
||||
std::map<std::string, DynamicPrintConfig> m_config_maps;
|
||||
std::map<std::string, std::string> m_filament_id_maps;
|
||||
|
||||
// Orca: Bundle metadata and cached preset names
|
||||
// std::map<std::string, BundleMetadata> m_bundles;
|
||||
fs::path dir_user_presets_local;
|
||||
fs::path dir_user_presets_subscribed;
|
||||
PresetBundleMetadata bundles;
|
||||
|
||||
struct ObsoletePresets
|
||||
{
|
||||
std::vector<std::string> prints;
|
||||
@@ -385,9 +504,14 @@ private:
|
||||
|
||||
// Orca: used for validation only
|
||||
bool validation_mode = false;
|
||||
std::string vendor_to_validate = "";
|
||||
std::string vendor_to_validate = "";
|
||||
int m_errors = 0;
|
||||
|
||||
// Helper function: save preset to bundle directory with common logic
|
||||
bool save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
|
||||
const std::string& bundle_id, const std::string& type_subdir,
|
||||
const std::string& bundle_base_dir);
|
||||
|
||||
};
|
||||
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
|
||||
|
||||
+13
-1
@@ -707,7 +707,19 @@ inline std::string filter_characters(const std::string& str, const std::string&
|
||||
return filteredStr;
|
||||
}
|
||||
|
||||
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target, std::function<bool(const std::string)> filter = nullptr);
|
||||
void copy_directory_recursively(const boost::filesystem::path& source,
|
||||
const boost::filesystem::path& target,
|
||||
std::function<bool(const std::string)> filter = nullptr,
|
||||
bool merge_mode = false);
|
||||
|
||||
// Install vendor bundles from resources directory to data directory
|
||||
// bundle_names: vector of vendor bundle names (without .json extension)
|
||||
// resource_subdir: subdirectory under resources_dir() (default: "profiles")
|
||||
// data_subdir: subdirectory under data_dir() (default: "system")
|
||||
// Returns: true if all bundles installed successfully, false otherwise
|
||||
bool install_vendor_bundles_from_resources(const std::vector<std::string>& bundle_names,
|
||||
const std::string& resource_subdir = "profiles",
|
||||
const std::string& data_subdir = "system");
|
||||
|
||||
// Orca: Since 1.7.9 Boost deprecated save_string_file and load_string_file, copy and modified from boost 1.7.8
|
||||
void save_string_file(const boost::filesystem::path& p, const std::string& str);
|
||||
|
||||
+76
-3
@@ -1597,12 +1597,15 @@ bool bbl_calc_md5(std::string &filename, std::string &md5_out)
|
||||
}
|
||||
|
||||
// SoftFever: copy directory recursively
|
||||
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target, std::function<bool(const std::string)> filter)
|
||||
void copy_directory_recursively(const boost::filesystem::path& source,
|
||||
const boost::filesystem::path& target,
|
||||
std::function<bool(const std::string)> filter,
|
||||
bool merge_mode)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << Slic3r::format("copy_directory_recursively %1% -> %2%", source, target);
|
||||
std::string error_message;
|
||||
|
||||
if (boost::filesystem::exists(target))
|
||||
if (!merge_mode && boost::filesystem::exists(target))
|
||||
boost::filesystem::remove_all(target);
|
||||
boost::filesystem::create_directories(target);
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(source))
|
||||
@@ -1613,7 +1616,7 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo
|
||||
|
||||
if (boost::filesystem::is_directory(dir_entry)) {
|
||||
const auto target_path = target / name;
|
||||
copy_directory_recursively(dir_entry, target_path);
|
||||
copy_directory_recursively(dir_entry, target_path, filter, merge_mode);
|
||||
}
|
||||
else {
|
||||
if(filter && filter(name))
|
||||
@@ -1630,6 +1633,76 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo
|
||||
return;
|
||||
}
|
||||
|
||||
bool install_vendor_bundles_from_resources(
|
||||
const std::vector<std::string>& bundle_names,
|
||||
const std::string& resource_subdir,
|
||||
const std::string& data_subdir)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
fs::path rsrc_path = fs::path(Slic3r::resources_dir()) / resource_subdir;
|
||||
fs::path vendor_path = fs::path(Slic3r::data_dir()) / data_subdir;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Installing " << bundle_names.size() << " bundles from resources...";
|
||||
|
||||
for (const auto &bundle : bundle_names) {
|
||||
try {
|
||||
// Install the JSON file
|
||||
auto path_in_rsrc = (rsrc_path / bundle).replace_extension(".json");
|
||||
auto path_in_vendors = (vendor_path / bundle).replace_extension(".json");
|
||||
|
||||
if (!fs::exists(path_in_rsrc)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Bundle not found in resources: " << bundle;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create target directory if needed
|
||||
if (!fs::exists(vendor_path))
|
||||
fs::create_directories(vendor_path);
|
||||
|
||||
// Copy JSON file
|
||||
std::string error_message;
|
||||
CopyFileResult cfr = copy_file(path_in_rsrc.string(), path_in_vendors.string(), error_message, false);
|
||||
if (cfr != CopyFileResult::SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to copy " << bundle << ".json: " << error_message;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the vendor directory (if it exists)
|
||||
auto dir_in_rsrc = rsrc_path / bundle;
|
||||
auto dir_in_vendors = vendor_path / bundle;
|
||||
|
||||
if (fs::exists(dir_in_rsrc) && fs::is_directory(dir_in_rsrc)) {
|
||||
// Remove existing directory
|
||||
if (fs::exists(dir_in_vendors))
|
||||
fs::remove_all(dir_in_vendors);
|
||||
fs::create_directories(dir_in_vendors);
|
||||
|
||||
// Copy with file filter (same as PresetUpdater::install_bundles_rsrc)
|
||||
// Filter out certain file types: .stl, .png, .svg, .jpeg, .jpg, .3mf
|
||||
auto file_filter = [](const std::string name) -> bool {
|
||||
return boost::iends_with(name, ".stl") ||
|
||||
boost::iends_with(name, ".png") ||
|
||||
boost::iends_with(name, ".svg") ||
|
||||
boost::iends_with(name, ".jpeg") ||
|
||||
boost::iends_with(name, ".jpg") ||
|
||||
boost::iends_with(name, ".3mf");
|
||||
};
|
||||
|
||||
copy_directory_recursively(dir_in_rsrc, dir_in_vendors, file_filter);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Successfully installed bundle: " << bundle;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Exception installing bundle " << bundle << ": " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_string_file(const boost::filesystem::path& p, const std::string& str)
|
||||
{
|
||||
boost::nowide::ofstream file;
|
||||
|
||||
@@ -377,6 +377,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/PlateSettingsDialog.hpp
|
||||
GUI/Preferences.cpp
|
||||
GUI/Preferences.hpp
|
||||
GUI/PresetBundleDialog.cpp
|
||||
GUI/PresetBundleDialog.hpp
|
||||
GUI/ExportPresetBundleDialog.cpp
|
||||
GUI/ExportPresetBundleDialog.hpp
|
||||
GUI/PresetComboBoxes.cpp
|
||||
GUI/PresetComboBoxes.hpp
|
||||
GUI/PresetHints.cpp
|
||||
|
||||
@@ -285,7 +285,7 @@ void PingCodeBindDialog::on_bind_printer(wxCommandEvent& event)
|
||||
}
|
||||
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (agent && agent->is_user_login() && ping_code.length() == PING_CODE_LENGTH) {
|
||||
if (agent && agent->is_user_login(wxGetApp().get_printer_cloud_provider()) && ping_code.length() == PING_CODE_LENGTH) {
|
||||
auto result = agent->ping_bind(ping_code.ToStdString());
|
||||
|
||||
if(result < 0){
|
||||
@@ -868,11 +868,12 @@ void BindMachineDialog::on_show(wxShowEvent &event)
|
||||
|
||||
m_printer_name->SetLabelText(from_u8(m_machine_info->get_dev_name()));
|
||||
|
||||
if (wxGetApp().is_user_login()) {
|
||||
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname());
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (wxGetApp().is_user_login(provider)) {
|
||||
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname(provider));
|
||||
m_user_name->SetLabelText(username_text);
|
||||
|
||||
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
|
||||
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar(provider);
|
||||
Slic3r::Http http = Slic3r::Http::get(avatar_url);
|
||||
std::string suffix = avatar_url.substr(avatar_url.find_last_of(".") + 1);
|
||||
http.header("accept", "image/" + suffix)
|
||||
@@ -1017,7 +1018,7 @@ void UnBindMachineDialog::on_cancel(wxCommandEvent &event)
|
||||
|
||||
void UnBindMachineDialog::on_unbind_printer(wxCommandEvent &event)
|
||||
{
|
||||
if (!wxGetApp().is_user_login()) {
|
||||
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider())) {
|
||||
m_status_text->SetLabelText(_L("Please log in first."));
|
||||
return;
|
||||
}
|
||||
@@ -1073,11 +1074,12 @@ void UnBindMachineDialog::on_show(wxShowEvent &event)
|
||||
m_printer_name->SetLabelText(from_u8(m_machine_info->get_dev_name()));
|
||||
|
||||
|
||||
if (wxGetApp().is_user_login()) {
|
||||
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_name());
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (wxGetApp().is_user_login(provider)) {
|
||||
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_name(provider));
|
||||
m_user_name->SetLabelText(username_text);
|
||||
|
||||
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
|
||||
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar(provider);
|
||||
Slic3r::Http http = Slic3r::Http::get(avatar_url);
|
||||
std::string suffix = avatar_url.substr(avatar_url.find_last_of(".") + 1);
|
||||
http.header("accept", "image/" + suffix)
|
||||
|
||||
@@ -244,13 +244,14 @@ void SelectMObjectPopup::Popup(wxWindow* WXUNUSED(focus))
|
||||
m_refresh_timer->Start(MACHINE_LIST_REFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
if (wxGetApp().is_user_login()) {
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (wxGetApp().is_user_login(provider)) {
|
||||
if (!get_print_info_thread) {
|
||||
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
|
||||
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = agent->get_user_print_info(&http_code, &body);
|
||||
int result = agent->get_user_print_info(&http_code, &body, provider);
|
||||
|
||||
wxGetApp().CallAfter([token, this, result, body]() {
|
||||
if (token.expired()) {return;}
|
||||
@@ -585,7 +586,7 @@ void CalibrationPanel::update_all() {
|
||||
// only disconnected server in cloud mode
|
||||
if (obj->connection_type() != "lan") {
|
||||
if (m_agent) {
|
||||
server_status = m_agent->is_server_connected() ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
|
||||
server_status = m_agent->is_server_connected(wxGetApp().get_printer_cloud_provider()) ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
|
||||
}
|
||||
}
|
||||
show_status((int)MONITOR_DISCONNECTED + server_status);
|
||||
|
||||
@@ -1694,7 +1694,7 @@ void CalibrationPresetPage::update_show_status()
|
||||
|
||||
MachineObject* obj_ = dev->get_selected_machine();
|
||||
if (!obj_) {
|
||||
if (agent->is_user_login()) {
|
||||
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
|
||||
show_status(CaliPresetPageStatus::CaliPresetStatusInvalidPrinter);
|
||||
}
|
||||
else {
|
||||
@@ -1704,7 +1704,7 @@ void CalibrationPresetPage::update_show_status()
|
||||
}
|
||||
|
||||
if (!obj_->is_lan_mode_printer()) {
|
||||
if (!agent->is_server_connected()) {
|
||||
if (!agent->is_server_connected(wxGetApp().get_printer_cloud_provider())) {
|
||||
show_status(CaliPresetPageStatus::CaliPresetStatusConnectingServer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,8 +248,7 @@ static bool delete_filament_preset_by_name(std::string delete_preset_name, std::
|
||||
if (!need_delete_preset) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't find delete preset and name: %1%") % delete_preset_name;
|
||||
if (!need_delete_preset->setting_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "delete preset = " << need_delete_preset->name << ", setting_id = " << need_delete_preset->setting_id;
|
||||
m_presets.set_sync_info_and_save(need_delete_preset->name, need_delete_preset->setting_id, "delete", 0);
|
||||
wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id);
|
||||
wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id, need_delete_preset->file);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't preset setting id is empty and name: %1%") % delete_preset_name;
|
||||
}
|
||||
@@ -4348,7 +4347,7 @@ void ExportConfigsDialog::data_init()
|
||||
|
||||
const std::deque<Preset> &filament_presets = preset_bundle.filaments.get_presets();
|
||||
for (const Preset &filament_preset : filament_presets) {
|
||||
if (filament_preset.is_system || filament_preset.is_default || filament_preset.is_project_embedded) continue;
|
||||
if (!filament_preset.is_user()) continue;
|
||||
if (filament_preset.is_compatible) {
|
||||
Preset *new_filament_preset = new Preset(filament_preset);
|
||||
m_filament_presets[preset_name].push_back(new_filament_preset);
|
||||
@@ -4357,7 +4356,7 @@ void ExportConfigsDialog::data_init()
|
||||
|
||||
const std::deque<Preset> &process_presets = preset_bundle.prints.get_presets();
|
||||
for (const Preset &process_preset : process_presets) {
|
||||
if (process_preset.is_system || process_preset.is_default || process_preset.is_project_embedded) continue;
|
||||
if (!process_preset.is_user()) continue;
|
||||
if (process_preset.is_compatible) {
|
||||
Preset *new_prpcess_preset = new Preset(process_preset);
|
||||
m_process_presets[preset_name].push_back(new_prpcess_preset);
|
||||
@@ -4371,7 +4370,7 @@ void ExportConfigsDialog::data_init()
|
||||
}
|
||||
const std::deque<Preset> &filament_presets = preset_bundle.filaments.get_presets();
|
||||
for (const Preset &filament_preset : filament_presets) {
|
||||
if (filament_preset.is_system || filament_preset.is_default) continue;
|
||||
if (!filament_preset.can_overwrite()) continue;
|
||||
Preset *new_filament_preset = new Preset(filament_preset);
|
||||
const Preset *base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset);
|
||||
|
||||
|
||||
@@ -18,6 +18,28 @@ namespace Slic3r
|
||||
{
|
||||
class MachineObject;
|
||||
|
||||
/**
|
||||
* DevAmsTray - Represents a single filament tray/slot in an AMS unit or virtual tray.
|
||||
*
|
||||
* Data Population:
|
||||
* ================
|
||||
* This class is used in two contexts with different population paths:
|
||||
*
|
||||
* 1. AMS Trays (within DevFilaSystem):
|
||||
* NetworkAgent → MachineObject::parse_json() → DevFilaSystemParser::ParseV1_0()
|
||||
*
|
||||
* 2. Virtual Trays (MachineObject::vt_slot for external/manual filament):
|
||||
* NetworkAgent → MachineObject::parse_json() → MachineObject::parse_vt_tray()
|
||||
*
|
||||
* Key Fields Used by build_filament_ams_list():
|
||||
* - setting_id: Filament preset identifier (tray_info_idx from printer)
|
||||
* - tag_uid: RFID tag unique ID for identifying filament spools
|
||||
* - m_fila_type: Filament material type (e.g., "PLA", "ABS", "PETG")
|
||||
* - color: Hex color string without '#' prefix (e.g., "FF0000")
|
||||
* - cols: Multi-color component list for gradient/multi-color filaments
|
||||
* - ctype: Color type indicator
|
||||
* - is_exists: Whether filament is currently loaded in the tray
|
||||
*/
|
||||
class DevAmsTray
|
||||
{
|
||||
public:
|
||||
@@ -83,6 +105,27 @@ public:
|
||||
static wxColour decode_color(const std::string& color);
|
||||
};
|
||||
|
||||
/**
|
||||
* DevAms - Represents a single AMS (Automatic Material System) unit.
|
||||
*
|
||||
* An AMS unit is a physical hardware component that holds multiple filament trays.
|
||||
* Different printer models support different AMS variants with varying slot counts
|
||||
* and capabilities.
|
||||
*
|
||||
* Data Population:
|
||||
* ================
|
||||
* Populated by DevFilaSystemParser from printer JSON messages received via NetworkAgent.
|
||||
*
|
||||
* Key Properties:
|
||||
* - m_ams_id: Unique identifier for this AMS unit (string, typically "0", "1", etc.)
|
||||
* - m_ext_id: Which extruder this AMS is connected to (for multi-extruder setups)
|
||||
* - m_trays: Map of tray IDs to DevAmsTray pointers containing filament data
|
||||
*
|
||||
* AMS Type Variants:
|
||||
* - AMS (type 1): Standard 4-slot AMS with humidity control
|
||||
* - AMS_LITE (type 2): Simplified version
|
||||
* - N3F/N3S (types 3,4): Newer variants with different humidity/drying support
|
||||
*/
|
||||
class DevAms
|
||||
{
|
||||
friend class DevFilaSystemParser;
|
||||
@@ -146,6 +189,36 @@ private:
|
||||
int m_left_dry_time = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* DevFilaSystem - Central manager for all AMS-related data on a printer.
|
||||
*
|
||||
* This class owns and manages the hierarchy of AMS units (DevAms) and their trays (DevAmsTray).
|
||||
* It provides the primary interface for querying filament/AMS state used by the GUI.
|
||||
*
|
||||
* Data Flow Architecture:
|
||||
* =======================
|
||||
* Printer Device (sends status via MQTT/LAN)
|
||||
* ↓
|
||||
* NetworkAgent (receives JSON, invokes registered callbacks)
|
||||
* ↓
|
||||
* MachineObject::parse_json() (delegates to DevFilaSystemParser)
|
||||
* ↓
|
||||
* DevFilaSystemParser::ParseV1_0() (populates this DevFilaSystem instance)
|
||||
* ↓
|
||||
* GUI functions like build_filament_ams_list() read from here
|
||||
*
|
||||
* Key Methods:
|
||||
* - GetAmsList(): Returns map of all AMS units (ams_id -> DevAms*)
|
||||
* - GetAmsTray(): Retrieves specific tray by AMS ID and tray ID
|
||||
* - HasAms(): Checks if any AMS units are connected
|
||||
*
|
||||
* Ownership:
|
||||
* - Owned by MachineObject (m_fila_system member)
|
||||
* - Owns all DevAms instances which in turn own DevAmsTray instances
|
||||
*
|
||||
* Note: This class does NOT directly communicate with NetworkAgent.
|
||||
* It is a passive data store populated by the parsing layer.
|
||||
*/
|
||||
class DevFilaSystem
|
||||
{
|
||||
friend class DevFilaSystemParser;
|
||||
@@ -200,6 +273,18 @@ private:
|
||||
};// class DevFilaSystem
|
||||
|
||||
|
||||
/**
|
||||
* DevFilaSystemParser - Parses printer JSON messages to populate DevFilaSystem.
|
||||
*
|
||||
* This is the bridge between NetworkAgent's raw JSON data and the structured
|
||||
* DevFilaSystem/DevAms/DevAmsTray hierarchy.
|
||||
*
|
||||
* Called from MachineObject::parse_json() when AMS-related fields are present
|
||||
* in printer status messages received via MQTT or LAN communication.
|
||||
*
|
||||
* @see MachineObject::parse_json() - Entry point for JSON parsing
|
||||
* @see DevFilaSystem - Target data structure
|
||||
*/
|
||||
class DevFilaSystemParser
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -363,7 +363,7 @@ namespace Slic3r
|
||||
return obj;
|
||||
}
|
||||
|
||||
int DeviceManager::query_bind_status(std::string& msg)
|
||||
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
|
||||
{
|
||||
if (!m_agent)
|
||||
{
|
||||
@@ -381,7 +381,7 @@ namespace Slic3r
|
||||
|
||||
unsigned int http_code;
|
||||
std::string http_body;
|
||||
int result = m_agent->query_bind_status(query_list, &http_code, &http_body);
|
||||
int result = m_agent->query_bind_status(query_list, &http_code, &http_body, provider);
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
@@ -419,9 +419,9 @@ namespace Slic3r
|
||||
return result;
|
||||
}
|
||||
|
||||
MachineObject* DeviceManager::get_user_machine(std::string dev_id)
|
||||
MachineObject* DeviceManager::get_user_machine(std::string dev_id, const std::string& provider)
|
||||
{
|
||||
if (!m_agent || !m_agent->is_user_login())
|
||||
if (!m_agent || !m_agent->is_user_login(provider))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -442,14 +442,13 @@ namespace Slic3r
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DeviceManager::clean_user_info()
|
||||
void DeviceManager::clean_user_info(bool keep_local_selection)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "DeviceManager::clean_user_info";
|
||||
// reset selected_machine
|
||||
selected_machine = "";
|
||||
local_selected_machine = "";
|
||||
|
||||
OnSelectedMachineChanged(selected_machine, "");
|
||||
const std::string previous_selected_machine = selected_machine;
|
||||
const bool keep_selected_machine = keep_local_selection &&
|
||||
!selected_machine.empty() &&
|
||||
localMachineList.find(selected_machine) != localMachineList.end();
|
||||
|
||||
// clean user list
|
||||
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
|
||||
@@ -462,6 +461,13 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
userMachineList.clear();
|
||||
|
||||
if (!keep_selected_machine) {
|
||||
selected_machine = "";
|
||||
local_selected_machine = "";
|
||||
}
|
||||
|
||||
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
|
||||
}
|
||||
|
||||
bool DeviceManager::set_selected_machine(std::string dev_id)
|
||||
@@ -566,7 +572,7 @@ namespace Slic3r
|
||||
{
|
||||
if (selected_machine.empty()) return nullptr;
|
||||
|
||||
MachineObject* obj = get_user_machine(selected_machine);
|
||||
MachineObject* obj = get_user_machine(selected_machine, GUI::wxGetApp().get_printer_cloud_provider());
|
||||
if (obj)
|
||||
return obj;
|
||||
|
||||
@@ -683,15 +689,15 @@ namespace Slic3r
|
||||
return "";
|
||||
}
|
||||
|
||||
void DeviceManager::modify_device_name(std::string dev_id, std::string dev_name)
|
||||
void DeviceManager::modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "modify_device_name";
|
||||
if (m_agent)
|
||||
{
|
||||
int result = m_agent->modify_printer_name(dev_id, dev_name);
|
||||
int result = m_agent->modify_printer_name(dev_id, dev_name, provider);
|
||||
if (result == 0)
|
||||
{
|
||||
update_user_machine_list_info();
|
||||
update_user_machine_list_info(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -712,6 +718,7 @@ namespace Slic3r
|
||||
try
|
||||
{
|
||||
json j = json::parse(body);
|
||||
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
|
||||
@@ -740,7 +747,7 @@ namespace Slic3r
|
||||
obj = new MachineObject(this, m_agent, "", "", "");
|
||||
if (m_agent)
|
||||
{
|
||||
obj->set_bind_status(m_agent->get_user_name());
|
||||
obj->set_bind_status(m_agent->get_user_name(provider));
|
||||
}
|
||||
|
||||
if (obj->get_dev_ip().empty())
|
||||
@@ -806,14 +813,14 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::update_user_machine_list_info()
|
||||
void DeviceManager::update_user_machine_list_info(const std::string& provider)
|
||||
{
|
||||
if (!m_agent) return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "update_user_machine_list_info";
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = m_agent->get_user_print_info(&http_code, &body);
|
||||
int result = m_agent->get_user_print_info(&http_code, &body, provider);
|
||||
if (result == 0)
|
||||
{
|
||||
parse_user_print_info(body);
|
||||
@@ -906,7 +913,7 @@ namespace Slic3r
|
||||
}
|
||||
|
||||
// do some refresh
|
||||
if (Slic3r::GUI::wxGetApp().is_user_login())
|
||||
if (Slic3r::GUI::wxGetApp().is_user_login(Slic3r::GUI::wxGetApp().get_printer_cloud_provider()))
|
||||
{
|
||||
m_manager->check_pushing();
|
||||
try
|
||||
@@ -922,4 +929,4 @@ namespace Slic3r
|
||||
// certificate
|
||||
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,14 +65,14 @@ public:
|
||||
std::map<std::string, MachineObject*> get_user_machinelist() const { return userMachineList; }
|
||||
std::string get_first_online_user_machine() const;
|
||||
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
|
||||
void clean_user_info();
|
||||
void clean_user_info(bool keep_local_selection = false);
|
||||
|
||||
void load_last_machine();
|
||||
void update_user_machine_list_info();
|
||||
void update_user_machine_list_info(const std::string& provider);
|
||||
void parse_user_print_info(std::string body);
|
||||
void reload_printer_settings();
|
||||
|
||||
MachineObject* get_user_machine(std::string dev_id);
|
||||
MachineObject* get_user_machine(std::string dev_id, const std::string& provider);
|
||||
|
||||
// subscribe
|
||||
void add_user_subscribe();
|
||||
@@ -83,11 +83,11 @@ public:
|
||||
MachineObject* get_my_machine(std::string dev_id);
|
||||
std::map<std::string, MachineObject*> get_my_machine_list();
|
||||
std::map<std::string, MachineObject*> get_my_cloud_machine_list();
|
||||
void modify_device_name(std::string dev_id, std::string dev_name);
|
||||
void modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider);
|
||||
|
||||
/* create machine or update machine properties */
|
||||
void on_machine_alive(std::string json_str);
|
||||
int query_bind_status(std::string& msg);
|
||||
int query_bind_status(std::string& msg, const std::string& provider);
|
||||
|
||||
// mutil-device
|
||||
void EnableMultiMachine(bool enable = true);
|
||||
|
||||
@@ -2430,7 +2430,7 @@ bool MachineObject::is_connected()
|
||||
if (!is_lan_mode_printer()) {
|
||||
NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
|
||||
if (m_agent) {
|
||||
return m_agent->is_server_connected();
|
||||
return m_agent->is_server_connected(Slic3r::GUI::wxGetApp().get_printer_cloud_provider());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,553 @@
|
||||
#include "ExportPresetBundleDialog.hpp"
|
||||
#include <slic3r/GUI/Widgets/WebView.hpp>
|
||||
#include "GUI_App.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <wx/app.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
#include <wx/string.h>
|
||||
#include <slic3r/GUI/Widgets/WebView.hpp>
|
||||
#include <miniz.h>
|
||||
#include <slic3r/GUI/MsgDialog.hpp>
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
ExportPresetBundleDialog::ExportPresetBundleDialog(
|
||||
wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
|
||||
: DPIDialog(parent, id, _L("ExportPresetBundle"), pos, size, style)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetMinSize(DESIGN_WINDOW_SIZE);
|
||||
Init();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
ExportPresetBundleDialog::~ExportPresetBundleDialog()
|
||||
{
|
||||
for (std::pair<std::string, Preset*> printer_preset : m_printer_presets) {
|
||||
Preset* preset = printer_preset.second;
|
||||
if (preset) {
|
||||
delete preset;
|
||||
preset = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::LoadUrl(wxString& url)
|
||||
{
|
||||
if (!m_browser)
|
||||
return;
|
||||
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " enter, url=" << url.ToStdString();
|
||||
WebView::LoadUrl(m_browser, url);
|
||||
m_browser->SetFocus();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " exit";
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::on_dpi_changed(const wxRect& suggested_rect) { this->Refresh(); }
|
||||
|
||||
void ExportPresetBundleDialog::Init()
|
||||
{
|
||||
wxString TargetUrl = from_u8(
|
||||
(boost::filesystem::path(resources_dir()) / "web/dialog/ExportPresetDialog/index.html").make_preferred().string());
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", strlang=%1%") % into_u8(strlang);
|
||||
if (strlang != "")
|
||||
TargetUrl = wxString::Format("%s?lang=%s", std::string(TargetUrl.mb_str()), strlang);
|
||||
TargetUrl = "file://" + TargetUrl;
|
||||
|
||||
// Create the webview
|
||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||
if (m_browser == nullptr) {
|
||||
wxLogError("Could not init m_browser");
|
||||
return;
|
||||
}
|
||||
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
SetTitle(_L("Export Preset Bundle"));
|
||||
SetSizer(topsizer);
|
||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
|
||||
// Set a more sensible size for web browsing
|
||||
wxSize pSize = FromDIP(wxSize(820, 660));
|
||||
SetSize(pSize);
|
||||
int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
|
||||
int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
|
||||
int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
|
||||
wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY);
|
||||
Move(tmpPT);
|
||||
|
||||
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ExportPresetBundleDialog::OnScriptMessage, this, m_browser->GetId());
|
||||
|
||||
LoadUrl(TargetUrl);
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::RunScript(const wxString& s)
|
||||
{
|
||||
if (!m_browser)
|
||||
return;
|
||||
|
||||
WebView::RunScript(m_browser, s);
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::OnScriptMessage(wxWebViewEvent& e)
|
||||
{
|
||||
try {
|
||||
wxString strInput = e.GetString();
|
||||
BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;OnRecv:" << strInput.c_str();
|
||||
json j = json::parse(strInput.utf8_string());
|
||||
|
||||
wxString strCmd = j["command"];
|
||||
BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;Command:" << strCmd;
|
||||
|
||||
if (strCmd == "close_page") {
|
||||
this->EndModal(wxID_CANCEL);
|
||||
} else if (strCmd == "request_export_preset_profile") {
|
||||
InitExportData();
|
||||
OnRequestPresets();
|
||||
} else if (strCmd == "export_local") {
|
||||
wxFileDialog dlg(this, _L("Save preset bundle"), "", "export.orca_bundle", "Orca Preset Bundle (*.orca_bundle)|*.orca_bundle",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
wxString path;
|
||||
wxString name;
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
path = dlg.GetPath();
|
||||
wxFileName file_name(path);
|
||||
name = file_name.GetName();
|
||||
if (file_name.GetExt().empty()) {
|
||||
file_name.SetExt("orca_bundle");
|
||||
path = file_name.GetFullPath();
|
||||
}
|
||||
}
|
||||
OnExportData(path, name, j["data"]);
|
||||
}
|
||||
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;Error:" << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_machine_name(const std::string& preset_name)
|
||||
{
|
||||
size_t index_at = preset_name.find_last_of("@");
|
||||
if (std::string::npos == index_at) {
|
||||
return "";
|
||||
} else {
|
||||
return preset_name.substr(index_at + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_filament_name(std::string& preset_name)
|
||||
{
|
||||
size_t index_at = preset_name.find_last_of("@");
|
||||
if (std::string::npos == index_at) {
|
||||
return preset_name;
|
||||
} else {
|
||||
return preset_name.substr(0, index_at - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_vendor_name(std::string& preset_name)
|
||||
{
|
||||
if (preset_name.empty())
|
||||
return "";
|
||||
std::string vendor_name = preset_name.substr(preset_name.find_first_not_of(' ')); // remove the name prefix space
|
||||
size_t index_at = vendor_name.find(" ");
|
||||
if (std::string::npos == index_at) {
|
||||
return vendor_name;
|
||||
} else {
|
||||
vendor_name = vendor_name.substr(0, index_at);
|
||||
return vendor_name;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_curr_time(const char* format = "%Y_%m_%d_%H_%M_%S")
|
||||
{
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
|
||||
std::time_t time = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::tm local_time = *std::localtime(&time);
|
||||
std::ostringstream time_stream;
|
||||
time_stream << std::put_time(&local_time, format);
|
||||
|
||||
std::string current_time = time_stream.str();
|
||||
return current_time;
|
||||
}
|
||||
|
||||
static mz_bool initial_zip_archive(mz_zip_archive& zip_archive, const std::string& file_path)
|
||||
{
|
||||
mz_zip_zero_struct(&zip_archive);
|
||||
mz_bool status;
|
||||
|
||||
// Initialize the ZIP file to write to the structure, using memory storage
|
||||
|
||||
std::string export_dir = encode_path(file_path.c_str());
|
||||
status = mz_zip_writer_init_file(&zip_archive, export_dir.c_str(), 0);
|
||||
return status;
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::InitExportData()
|
||||
{
|
||||
// Delete the Temp folder
|
||||
boost::filesystem::path folder(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp");
|
||||
if (boost::filesystem::exists(folder))
|
||||
boost::filesystem::remove_all(folder);
|
||||
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
bool temp_folder_exist = true;
|
||||
if (!boost::filesystem::exists(user_folder)) {
|
||||
if (!boost::filesystem::create_directories(user_folder, ec)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << user_folder << " " << ec.message();
|
||||
temp_folder_exist = false;
|
||||
}
|
||||
}
|
||||
boost::filesystem::path temp_folder(user_folder / "Temp");
|
||||
if (!boost::filesystem::exists(temp_folder)) {
|
||||
if (!boost::filesystem::create_directories(temp_folder, ec)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << temp_folder << " " << ec.message();
|
||||
temp_folder_exist = false;
|
||||
}
|
||||
}
|
||||
if (!temp_folder_exist) {
|
||||
MessageDialog dlg(this, _L("Failed to create temporary folder, please try Export Configs again."),
|
||||
wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
|
||||
dlg.ShowModal();
|
||||
EndModal(wxCANCEL);
|
||||
}
|
||||
|
||||
PresetBundle preset_bundle(*wxGetApp().preset_bundle);
|
||||
|
||||
const std::deque<Preset>& printer_presets = preset_bundle.printers.get_presets();
|
||||
// for all the printers
|
||||
for (const Preset& printer_preset : printer_presets) {
|
||||
std::string preset_name = printer_preset.name;
|
||||
if (!printer_preset.is_visible || printer_preset.is_default || printer_preset.is_project_embedded)
|
||||
continue;
|
||||
if (preset_bundle.printers.select_preset_by_name(preset_name, true)) {
|
||||
preset_bundle.update_compatible(PresetSelectCompatibleType::Always);
|
||||
|
||||
const std::deque<Preset>& filament_presets = preset_bundle.filaments.get_presets();
|
||||
for (const Preset& filament_preset : filament_presets) {
|
||||
if (!filament_preset.is_user())
|
||||
continue;
|
||||
if (filament_preset.is_compatible) {
|
||||
Preset* new_filament_preset = new Preset(filament_preset);
|
||||
m_filament_presets[preset_name].push_back(new_filament_preset);
|
||||
}
|
||||
}
|
||||
|
||||
const std::deque<Preset>& process_presets = preset_bundle.prints.get_presets();
|
||||
for (const Preset& process_preset : process_presets) {
|
||||
if (!process_preset.is_user())
|
||||
continue;
|
||||
if (process_preset.is_compatible) {
|
||||
Preset* new_prpcess_preset = new Preset(process_preset);
|
||||
m_process_presets[preset_name].push_back(new_prpcess_preset);
|
||||
}
|
||||
}
|
||||
// make new and erase sensitive information
|
||||
Preset* new_printer_preset = new Preset(printer_preset);
|
||||
if (new_printer_preset->type == Preset::Type::TYPE_PRINTER) {
|
||||
boost::filesystem::path file_path(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp" + "/" +
|
||||
(new_printer_preset->name + ".json"));
|
||||
new_printer_preset->file = file_path.make_preferred().string();
|
||||
|
||||
DynamicPrintConfig& config = new_printer_preset->config;
|
||||
config.erase("print_host");
|
||||
config.erase("print_host_webui");
|
||||
config.erase("printhost_apikey");
|
||||
config.erase("printhost_cafile");
|
||||
config.erase("printhost_user");
|
||||
config.erase("printhost_password");
|
||||
config.erase("printhost_port");
|
||||
|
||||
new_printer_preset->save(nullptr);
|
||||
}
|
||||
m_printer_presets[preset_name] = new_printer_preset;
|
||||
}
|
||||
}
|
||||
const std::deque<Preset>& filament_presets = preset_bundle.filaments.get_presets();
|
||||
for (const Preset& filament_preset : filament_presets) {
|
||||
if (!filament_preset.can_overwrite())
|
||||
continue;
|
||||
Preset* new_filament_preset = new Preset(filament_preset);
|
||||
const Preset* base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset);
|
||||
|
||||
if (base_filament_preset == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to find base preset";
|
||||
continue;
|
||||
}
|
||||
std::string filament_preset_name = base_filament_preset->name;
|
||||
std::string machine_name = get_machine_name(filament_preset_name);
|
||||
m_filament_name_to_presets[get_filament_name(filament_preset_name)].push_back(
|
||||
std::make_pair(get_vendor_name(machine_name), new_filament_preset));
|
||||
}
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::OnRequestPresets()
|
||||
{
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
json res;
|
||||
res["command"] = "response_export_preset_profile";
|
||||
res["sequence_id"] = "2000";
|
||||
res["data"] = json::object();
|
||||
|
||||
res["data"]["printers"] = json::array();
|
||||
res["data"]["filaments"] = json::array();
|
||||
res["data"]["process"] = json::array();
|
||||
|
||||
for (std::pair<std::string, Preset*> preset : m_printer_presets) {
|
||||
if (preset.second->is_system)
|
||||
continue;
|
||||
res["data"]["printers"].push_back(preset.first);
|
||||
}
|
||||
|
||||
for (std::pair<std::string, std::vector<std::pair<std::string, Preset*>>> filament_name_to_preset : m_filament_name_to_presets) {
|
||||
if (filament_name_to_preset.second.empty())
|
||||
continue;
|
||||
res["data"]["filaments"].push_back(filament_name_to_preset.first);
|
||||
}
|
||||
|
||||
for (std::pair<std::string, std::vector<const Preset*>> presets : m_process_presets) {
|
||||
Preset* printer_preset = preset_bundle->printers.find_preset(presets.first, false);
|
||||
if (!printer_preset)
|
||||
continue;
|
||||
if (!printer_preset->is_system)
|
||||
continue;
|
||||
if (preset_bundle->printers.get_preset_base(*printer_preset) != printer_preset)
|
||||
continue;
|
||||
for (const Preset* preset : presets.second) {
|
||||
if (!preset->is_system) {
|
||||
res["data"]["process"].push_back(preset->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", wxString::FromUTF8(res.dump(-1, ' ', false, json::error_handler_t::ignore)));
|
||||
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::OnExportData(const wxString& path, const wxString& filename, json data)
|
||||
{
|
||||
auto get_names = [&](const char* key) {
|
||||
std::vector<std::string> out;
|
||||
auto it = data.find(key);
|
||||
if (it == data.end() || !it->is_array())
|
||||
return out;
|
||||
for (const auto& v : *it) {
|
||||
if (v.is_string())
|
||||
out.push_back(v.get<std::string>());
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// JS BuildResultPayload uses: machines / filaments / presets
|
||||
const auto machine_names = get_names("machines");
|
||||
const auto filament_names = get_names("filaments");
|
||||
const auto process_names = get_names("presets"); // or "process" if your JS sends that
|
||||
|
||||
std::vector<Preset*> selected_printers;
|
||||
for (const auto& name : machine_names) {
|
||||
auto it = m_printer_presets.find(name);
|
||||
if (it != m_printer_presets.end() && it->second)
|
||||
selected_printers.push_back(it->second);
|
||||
}
|
||||
|
||||
std::vector<Preset*> selected_filaments;
|
||||
for (const auto& name : filament_names) {
|
||||
auto it = m_filament_name_to_presets.find(name); // name -> vector<pair<vendor, Preset*>>
|
||||
if (it == m_filament_name_to_presets.end())
|
||||
continue;
|
||||
for (const auto& vp : it->second) {
|
||||
if (vp.second)
|
||||
selected_filaments.push_back(vp.second);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Preset*> selected_processes;
|
||||
for (const auto& name : process_names) {
|
||||
if (Preset* p = wxGetApp().preset_bundle->prints.find_preset(name, false))
|
||||
selected_processes.push_back(p);
|
||||
}
|
||||
|
||||
std::string export_path = into_u8(path);
|
||||
if (export_path.empty() || "initial_failed" == export_path)
|
||||
return;
|
||||
|
||||
boost::filesystem::path export_file_path = boost::filesystem::path(export_path).make_preferred();
|
||||
if (export_file_path.extension().empty())
|
||||
export_file_path += ".orca_bundle";
|
||||
|
||||
const boost::filesystem::path export_dir = export_file_path.parent_path();
|
||||
if (!export_dir.empty() && !boost::filesystem::exists(export_dir)) {
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::create_directories(export_dir, ec)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << export_dir << " " << ec.message();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " export file path: " << export_file_path.string();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Export printer preset bundle";
|
||||
|
||||
json bundle_structure;
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
std::string clock = get_curr_time();
|
||||
if (agent) {
|
||||
bundle_structure["version"] = agent->get_version();
|
||||
bundle_structure["bundle_id"] = agent->get_user_id() + "_" + std::string(filename.utf8_string()) + "_" + clock;
|
||||
} else {
|
||||
bundle_structure["version"] = "";
|
||||
std::string id;
|
||||
bundle_structure["bundle_id"] = id + "offline_" + "_" + clock;
|
||||
}
|
||||
bundle_structure["bundle_type"] = "printer config bundle";
|
||||
bundle_structure["printer_preset_name"] = "export";
|
||||
json printer_config = json::array();
|
||||
json filament_configs = json::array();
|
||||
json process_configs = json::array();
|
||||
mz_zip_archive zip_archive;
|
||||
mz_bool status = initial_zip_archive(zip_archive, export_file_path.string());
|
||||
if (MZ_FALSE == status) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Failed to initialize ZIP archive";
|
||||
show_export_result(ExportCase::INITIALIZE_FAIL);
|
||||
}
|
||||
for (auto& printer : selected_printers) {
|
||||
boost::filesystem::path printer_file_path = boost::filesystem::path(printer->file);
|
||||
std::string preset_path = printer_file_path.make_preferred().string();
|
||||
if (preset_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Export printer preset: " << printer->name << " skip because of the preset file path is empty.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add a file to the ZIP file
|
||||
std::string printer_config_file_name = "printer/" + printer_file_path.filename().string();
|
||||
status = mz_zip_writer_add_file(&zip_archive, printer_config_file_name.c_str(), encode_path(preset_path.c_str()).c_str(), NULL, 0,
|
||||
MZ_DEFAULT_COMPRESSION);
|
||||
if (MZ_FALSE == status) {
|
||||
BOOST_LOG_TRIVIAL(info) << printer->name << " Failed to add file to ZIP archive";
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
show_export_result(ExportCase::ADD_FILE_FAIL);
|
||||
return;
|
||||
}
|
||||
printer_config.push_back(printer_config_file_name);
|
||||
BOOST_LOG_TRIVIAL(info) << "Printer preset json add successful: " << printer->name;
|
||||
}
|
||||
for (auto& filament : selected_filaments) {
|
||||
boost::filesystem::path filament_file_path = boost::filesystem::path(filament->file);
|
||||
std::string filament_preset_path = filament_file_path.make_preferred().string();
|
||||
if (filament_preset_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Export filament preset: " << filament->name << " skip because of the preset file path is empty.";
|
||||
continue;
|
||||
}
|
||||
std::string filament_config_file_name = "filament/" + filament_file_path.filename().string();
|
||||
status = mz_zip_writer_add_file(&zip_archive, filament_config_file_name.c_str(), encode_path(filament_preset_path.c_str()).c_str(),
|
||||
NULL, 0, MZ_DEFAULT_COMPRESSION);
|
||||
if (MZ_FALSE == status) {
|
||||
BOOST_LOG_TRIVIAL(info) << filament->name << " Failed to add file to ZIP archive";
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
show_export_result(ExportCase::ADD_FILE_FAIL);
|
||||
return;
|
||||
}
|
||||
filament_configs.push_back(filament_config_file_name);
|
||||
BOOST_LOG_TRIVIAL(info) << "Filament preset json add successful.";
|
||||
}
|
||||
|
||||
for (auto& process : selected_processes) {
|
||||
boost::filesystem::path process_file_path = boost::filesystem::path(process->file);
|
||||
std::string process_preset_path = process_file_path.make_preferred().string();
|
||||
if (process_preset_path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Export process preset: " << process->name << " skip because of the preset file path is empty.";
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string process_config_file_name = "process/" + process_file_path.filename().string();
|
||||
status = mz_zip_writer_add_file(&zip_archive, process_config_file_name.c_str(), encode_path(process_preset_path.c_str()).c_str(),
|
||||
NULL, 0, MZ_DEFAULT_COMPRESSION);
|
||||
if (MZ_FALSE == status) {
|
||||
BOOST_LOG_TRIVIAL(info) << process->name << " Failed to add file to ZIP archive";
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
show_export_result(ExportCase::ADD_FILE_FAIL);
|
||||
return;
|
||||
}
|
||||
process_configs.push_back(process_config_file_name);
|
||||
BOOST_LOG_TRIVIAL(info) << "Process preset json add successful: ";
|
||||
}
|
||||
|
||||
bundle_structure["printer_config"] = printer_config;
|
||||
bundle_structure["filament_config"] = filament_configs;
|
||||
bundle_structure["process_config"] = process_configs;
|
||||
|
||||
std::string bundle_structure_str = bundle_structure.dump();
|
||||
status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(),
|
||||
MZ_DEFAULT_COMPRESSION);
|
||||
if (MZ_FALSE == status) {
|
||||
BOOST_LOG_TRIVIAL(info) << " Failed to add file: " << BUNDLE_STRUCTURE_JSON_NAME;
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
show_export_result(ExportCase::ADD_BUNDLE_STRUCTURE_FAIL);
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << " Success to add file: " << BUNDLE_STRUCTURE_JSON_NAME;
|
||||
|
||||
// Complete writing of ZIP file
|
||||
mz_bool s = mz_zip_writer_finalize_archive(&zip_archive);
|
||||
if (MZ_FALSE == s) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Failed to finalize ZIP archive";
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
show_export_result(ExportCase::FINALIZE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Release ZIP file to write structure and related resources
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
// if (ExportCase::CASE_COUNT != save_result) return save_result;
|
||||
BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully";
|
||||
}
|
||||
|
||||
void ExportPresetBundleDialog::show_export_result(const ExportCase& export_case)
|
||||
{
|
||||
MessageDialog* msg_dlg = nullptr;
|
||||
switch (export_case) {
|
||||
case ExportCase::INITIALIZE_FAIL:
|
||||
msg_dlg = new MessageDialog(this, _L("initialize fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
break;
|
||||
case ExportCase::ADD_FILE_FAIL:
|
||||
msg_dlg = new MessageDialog(this, _L("add file fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
break;
|
||||
case ExportCase::ADD_BUNDLE_STRUCTURE_FAIL:
|
||||
msg_dlg = new MessageDialog(this, _L("add bundle structure file fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
break;
|
||||
case ExportCase::FINALIZE_FAIL:
|
||||
msg_dlg = new MessageDialog(this, _L("finalize fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
break;
|
||||
case ExportCase::OPEN_ZIP_WRITTEN_FILE:
|
||||
msg_dlg = new MessageDialog(this, _L("open zip written fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
break;
|
||||
case ExportCase::EXPORT_SUCCESS:
|
||||
msg_dlg = new MessageDialog(this, _L("Export successful"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
|
||||
wxYES | wxYES_DEFAULT | wxCENTRE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg_dlg) {
|
||||
msg_dlg->ShowModal();
|
||||
delete msg_dlg;
|
||||
msg_dlg = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef slic3r_ExportPresetBundleDialog_hpp_
|
||||
#define slic3r_ExportPresetBundleDialog_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/language.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/fswatcher.h>
|
||||
#include <wx/webview.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
#define DESIGN_GRAY900_COLOR wxColour("#363636") // Label color
|
||||
#define DESIGN_GRAY600_COLOR wxColour("#ACACAC") // Dimmed text color
|
||||
|
||||
#define DESIGN_WINDOW_SIZE wxSize(FromDIP(640), FromDIP(640))
|
||||
#define DESIGN_TITLE_SIZE wxSize(FromDIP(280), -1)
|
||||
#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
|
||||
#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
|
||||
#define DESIGN_INPUT_SIZE wxSize(FromDIP(120), -1)
|
||||
#define DESIGN_LEFT_MARGIN 25
|
||||
#define VERTICAL_GAP_SIZE FromDIP(4)
|
||||
|
||||
enum ExportCase {
|
||||
INITIALIZE_FAIL = 0,
|
||||
ADD_FILE_FAIL,
|
||||
ADD_BUNDLE_STRUCTURE_FAIL,
|
||||
FINALIZE_FAIL,
|
||||
OPEN_ZIP_WRITTEN_FILE,
|
||||
EXPORT_CANCEL,
|
||||
EXPORT_SUCCESS,
|
||||
CASE_COUNT,
|
||||
};
|
||||
|
||||
class ExportPresetBundleDialog : public Slic3r::GUI::DPIDialog
|
||||
{
|
||||
public:
|
||||
ExportPresetBundleDialog(wxWindow* parent,
|
||||
wxWindowID id = wxID_ANY,
|
||||
const wxString& title = wxT(""),
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX);
|
||||
|
||||
~ExportPresetBundleDialog();
|
||||
|
||||
// Utilities
|
||||
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
|
||||
bool recreate_GUI() const { return m_recreate_GUI; }
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void show_export_result(const ExportCase& e);
|
||||
|
||||
void Init();
|
||||
void InitExportData();
|
||||
|
||||
// Webview
|
||||
void LoadUrl(wxString& url);
|
||||
void OnScriptMessage(wxWebViewEvent& e);
|
||||
void RunScript(const wxString& s);
|
||||
void OnRequestPresets();
|
||||
void OnExportData(const wxString& path, const wxString& name, json data);
|
||||
|
||||
protected:
|
||||
bool m_seq_top_layer_only_changed{false};
|
||||
bool m_recreate_GUI{false};
|
||||
|
||||
// Webview
|
||||
wxWebView* m_browser{nullptr};
|
||||
|
||||
// Export Preset
|
||||
std::unordered_map<std::string, Preset*> m_printer_presets; // first: printer name, second: printer presets have same printer name
|
||||
std::unordered_map<std::string, std::vector<const Preset*>>
|
||||
m_filament_presets; // first: printer name, second: filament presets have same printer name
|
||||
std::unordered_map<std::string, std::vector<const Preset*>>
|
||||
m_process_presets; // first: printer name, second: filament presets have same printer name
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, Preset*>>>
|
||||
m_filament_name_to_presets; // first: filament name, second presets have same filament name and printer name in vector
|
||||
};
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
@@ -524,15 +525,6 @@ void about()
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
void login()
|
||||
{
|
||||
//LoginDialog dlg;
|
||||
//dlg.ShowModal();
|
||||
|
||||
ZUserLogin dlg;
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
void desktop_open_datadir_folder()
|
||||
{
|
||||
// Execute command to open a file explorer, platform dependent.
|
||||
|
||||
@@ -79,8 +79,6 @@ boost::filesystem::path into_path(const wxString &str);
|
||||
|
||||
// Display an About dialog
|
||||
extern void about();
|
||||
// Display a Login dialog
|
||||
extern void login();
|
||||
// Ask the destop to open the datadir using the default file explorer.
|
||||
extern void desktop_open_datadir_folder();
|
||||
// Ask the destop to open one folder
|
||||
|
||||
+1092
-259
@@ -1,13 +1,17 @@
|
||||
#include "ExportPresetBundleDialog.hpp"
|
||||
#include "OrcaCloudServiceAgent.hpp"
|
||||
#include "libslic3r/Technologies.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_Init.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_Factories.hpp"
|
||||
#include "slic3r/GUI/UserManager.hpp"
|
||||
#include "slic3r/GUI/TaskManager.hpp"
|
||||
#include "format.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
#include "Downloader.hpp"
|
||||
#include <boost/chrono/duration.hpp>
|
||||
#include <boost/log/detail/native_typeof.hpp>
|
||||
#include <wx/event.h>
|
||||
|
||||
// Localization headers: include libslic3r version first so everything in this file
|
||||
// uses the slic3r/GUI version (the macros will take precedence over the functions).
|
||||
@@ -244,19 +248,6 @@ std::string VersionInfo::convert_short_version(std::string full_version)
|
||||
return full_version;
|
||||
}
|
||||
|
||||
static std::string convert_studio_language_to_api(std::string lang_code)
|
||||
{
|
||||
boost::replace_all(lang_code, "_", "-");
|
||||
return lang_code;
|
||||
|
||||
/*if (lang_code == "zh_CN")
|
||||
return "zh-hans";
|
||||
else if (lang_code == "zh_TW")
|
||||
return "zh-hant";
|
||||
else
|
||||
return "en";*/
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool is_associate_files(std::wstring extend)
|
||||
{
|
||||
@@ -1014,9 +1005,10 @@ void GUI_App::post_init()
|
||||
}
|
||||
|
||||
this->check_new_version_sf();
|
||||
if (is_user_login() && !app_config->get_stealth_mode()) {
|
||||
const auto cloud_provider = get_printer_cloud_provider();
|
||||
if (is_user_login(cloud_provider) && !app_config->get_stealth_mode()) {
|
||||
// this->check_privacy_version(0);
|
||||
request_user_handle(0);
|
||||
request_user_handle(0, cloud_provider);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1034,8 +1026,8 @@ void GUI_App::post_init()
|
||||
});
|
||||
}
|
||||
);
|
||||
m_agent->set_on_http_error_fn([this](unsigned int status, std::string body) {
|
||||
this->handle_http_error(status, body);
|
||||
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||
this->handle_http_error(status, body, event.provider);
|
||||
});
|
||||
m_agent->start_discovery(true, false);
|
||||
}
|
||||
@@ -1091,6 +1083,9 @@ wxDEFINE_EVENT(EVT_ENTER_FORCE_UPGRADE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_SHOW_NO_NEW_VERSION, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_SHOW_DIALOG, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_CONNECT_LAN_MODE_PRINT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_UPDATE_PRESET_BUNDLE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_UPDATE_BUNDLE_COMPLETE, wxCommandEvent);
|
||||
|
||||
IMPLEMENT_APP(GUI_App)
|
||||
|
||||
//BBS: remove GCodeViewer as seperate APP logic
|
||||
@@ -1131,6 +1126,7 @@ void GUI_App::shutdown()
|
||||
}
|
||||
|
||||
if (m_is_recreating_gui) return;
|
||||
stop_http_server();
|
||||
set_closing(true);
|
||||
BOOST_LOG_TRIVIAL(info) << "GUI_App::shutdown exit";
|
||||
}
|
||||
@@ -1245,15 +1241,16 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
|
||||
fs::path tmp_path = target_file_path;
|
||||
tmp_path += format(".%1%%2%", get_current_pid(), ".tmp");
|
||||
|
||||
// Determine OS type for plugin download (must be set per-request since global
|
||||
// extra headers are no longer initialised on this branch).
|
||||
#if defined(__WINDOWS__)
|
||||
if (is_running_on_arm64() && !NetworkAgent::use_legacy_network) {
|
||||
//set to arm64 for plugins
|
||||
std::map<std::string, std::string> current_headers = Slic3r::Http::get_extra_headers();
|
||||
current_headers["X-BBL-OS-Type"] = "windows_arm";
|
||||
|
||||
Slic3r::Http::set_extra_headers(current_headers);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("download_plugin: set X-BBL-OS-Type to windows_arm");
|
||||
}
|
||||
std::string os_type = (is_running_on_arm64() && !NetworkAgent::use_legacy_network) ? "windows_arm" : "windows";
|
||||
#elif defined(__APPLE__)
|
||||
std::string os_type = "macos";
|
||||
#elif defined(__linux__)
|
||||
std::string os_type = "linux";
|
||||
#else
|
||||
std::string os_type = "windows";
|
||||
#endif
|
||||
|
||||
// get_url
|
||||
@@ -1263,6 +1260,7 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
|
||||
BOOST_LOG_TRIVIAL(info) << "[download_plugin]: check the plugin from " << url;
|
||||
http_url.timeout_connect(TIMEOUT_CONNECT)
|
||||
.timeout_max(TIMEOUT_RESPONSE)
|
||||
.header("X-BBL-OS-Type", os_type)
|
||||
.on_complete(
|
||||
[&download_url](std::string body, unsigned status) {
|
||||
try {
|
||||
@@ -1313,17 +1311,6 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
|
||||
result = -1;
|
||||
}).perform_sync();
|
||||
|
||||
#if defined(__WINDOWS__)
|
||||
if (is_running_on_arm64() && !NetworkAgent::use_legacy_network) {
|
||||
//set back
|
||||
std::map<std::string, std::string> current_headers = Slic3r::Http::get_extra_headers();
|
||||
current_headers["X-BBL-OS-Type"] = "windows";
|
||||
|
||||
Slic3r::Http::set_extra_headers(current_headers);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("download_plugin: set X-BBL-OS-Type back to windows");
|
||||
}
|
||||
#endif
|
||||
|
||||
bool cancel = false;
|
||||
if (result < 0) {
|
||||
j["result"] = "failed";
|
||||
@@ -1355,7 +1342,8 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
|
||||
// download
|
||||
Slic3r::Http http = Slic3r::Http::get(download_url);
|
||||
int reported_percent = 0;
|
||||
http.on_progress(
|
||||
http.header("X-BBL-OS-Type", os_type)
|
||||
.on_progress(
|
||||
[this, &pro_fn, cancel_fn, &result, &reported_percent, &err_msg](Slic3r::Http::Progress progress, bool& cancel) {
|
||||
int percent = 0;
|
||||
if (progress.dltotal != 0)
|
||||
@@ -1638,8 +1626,8 @@ void GUI_App::restart_networking()
|
||||
});
|
||||
}
|
||||
);
|
||||
m_agent->set_on_http_error_fn([this](unsigned int status, std::string body) {
|
||||
this->handle_http_error(status, body);
|
||||
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
|
||||
this->handle_http_error(status, body, event.provider);
|
||||
});
|
||||
m_agent->start_discovery(true, false);
|
||||
if (mainframe)
|
||||
@@ -1759,7 +1747,6 @@ bool GUI_App::hot_reload_network_plugin()
|
||||
// Phase 1: Clear all callbacks (stops new invocations)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 1 - clearing callbacks";
|
||||
m_agent->set_on_ssdp_msg_fn(nullptr);
|
||||
m_agent->set_on_user_login_fn(nullptr);
|
||||
m_agent->set_on_printer_connected_fn(nullptr);
|
||||
m_agent->set_on_server_connected_fn(nullptr);
|
||||
m_agent->set_on_http_error_fn(nullptr);
|
||||
@@ -1975,10 +1962,6 @@ void GUI_App::init_networking_callbacks()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": enter, m_agent=%1%")%m_agent;
|
||||
if (m_agent) {
|
||||
//set callbacks
|
||||
//m_agent->set_on_user_login_fn([this](int online_login, bool login) {
|
||||
// GUI::wxGetApp().request_user_handle(online_login);
|
||||
// });
|
||||
|
||||
m_agent->set_server_callback([](std::string url, int status) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": server_callback, url=%1%, status=%2%") % url % status;
|
||||
//CallAfter([this]() {
|
||||
@@ -2006,13 +1989,13 @@ void GUI_App::init_networking_callbacks()
|
||||
});
|
||||
|
||||
|
||||
m_agent->set_on_server_connected_fn([this](int return_code, int reason_code) {
|
||||
m_agent->set_on_server_connected_fn([this](CloudEvent event, int return_code, int reason_code) {
|
||||
if (is_closing()) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
if (return_code == 5) {
|
||||
GUI::wxGetApp().CallAfter([this] {
|
||||
this->request_user_logout();
|
||||
GUI::wxGetApp().CallAfter([this, provider = event.provider] {
|
||||
this->request_user_logout(provider);
|
||||
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
|
||||
if (msg_dlg.ShowModal() == wxOK) {
|
||||
return;
|
||||
@@ -2020,36 +2003,38 @@ void GUI_App::init_networking_callbacks()
|
||||
});
|
||||
return;
|
||||
}
|
||||
GUI::wxGetApp().CallAfter([this] {
|
||||
GUI::wxGetApp().CallAfter([this, provider = event.provider] {
|
||||
if (is_closing())
|
||||
return;
|
||||
BOOST_LOG_TRIVIAL(trace) << "static: server connected";
|
||||
if (provider != this->get_printer_cloud_provider()) {
|
||||
return;
|
||||
}
|
||||
m_agent->set_user_selected_machine(m_agent->get_user_selected_machine());
|
||||
if (this->is_enable_multi_machine()) {
|
||||
auto evt = new wxCommandEvent(EVT_UPDATE_MACHINE_LIST);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
m_agent->set_user_selected_machine(m_agent->get_user_selected_machine());
|
||||
if (m_agent->is_user_login(provider)) {
|
||||
|
||||
/*disconnect lan*/
|
||||
DeviceManager* dev = this->getDeviceManager();
|
||||
if (!dev) return;
|
||||
|
||||
MachineObject *obj = dev->get_selected_machine();
|
||||
if (!obj) return;
|
||||
|
||||
/* resubscribe the cache dev list */
|
||||
if (this->is_enable_multi_machine()) {
|
||||
auto evt = new wxCommandEvent(EVT_UPDATE_MACHINE_LIST);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
m_agent->set_user_selected_machine(m_agent->get_user_selected_machine());
|
||||
//subscribe device
|
||||
if (m_agent->is_user_login()) {
|
||||
|
||||
/*disconnect lan*/
|
||||
DeviceManager* dev = this->getDeviceManager();
|
||||
if (!dev) return;
|
||||
|
||||
MachineObject *obj = dev->get_selected_machine();
|
||||
if (!obj) return;
|
||||
|
||||
/* resubscribe the cache dev list */
|
||||
if (this->is_enable_multi_machine()) {
|
||||
|
||||
if (!dev->subscribe_list_cache.empty()) {
|
||||
dev->subscribe_device_list(dev->subscribe_list_cache);
|
||||
}
|
||||
if (!dev->subscribe_list_cache.empty()) {
|
||||
dev->subscribe_device_list(dev->subscribe_list_cache);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
m_agent->set_on_printer_connected_fn([this](std::string dev_id) {
|
||||
if (is_closing()) {
|
||||
@@ -2168,7 +2153,8 @@ void GUI_App::init_networking_callbacks()
|
||||
return;
|
||||
}
|
||||
|
||||
if (MachineObject* obj = this->m_device_manager->get_user_machine(dev_id)) {
|
||||
const std::string provider = this->get_printer_cloud_provider();
|
||||
if (MachineObject* obj = this->m_device_manager->get_user_machine(dev_id, provider)) {
|
||||
auto sel = this->m_device_manager->get_selected_machine();
|
||||
if (sel && sel->get_dev_id() == dev_id) {
|
||||
obj->parse_json("cloud", msg);
|
||||
@@ -2194,7 +2180,7 @@ void GUI_App::init_networking_callbacks()
|
||||
return;
|
||||
|
||||
//check user
|
||||
if (user_id == m_agent->get_user_id()) {
|
||||
if (user_id == m_agent->get_user_id(get_printer_cloud_provider())) {
|
||||
this->m_user_manager->parse_json(msg);
|
||||
}
|
||||
|
||||
@@ -2497,60 +2483,14 @@ void GUI_App::copy_older_config()
|
||||
preset_bundle->copy_files(m_older_data_dir_path);
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> GUI_App::get_extra_header()
|
||||
{
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
extra_headers.insert(std::make_pair("X-BBL-Client-Type", "slicer"));
|
||||
extra_headers.insert(std::make_pair("X-BBL-Client-Name", SLIC3R_APP_NAME));
|
||||
extra_headers.insert(std::make_pair("X-BBL-Client-Version", get_bbl_client_version()));
|
||||
#if defined(__WINDOWS__)
|
||||
#ifdef _M_X64
|
||||
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "windows"));
|
||||
#else
|
||||
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "windows_arm"));
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "macos"));
|
||||
#elif defined(__LINUX__)
|
||||
extra_headers.insert(std::make_pair("X-BBL-OS-Type", "linux"));
|
||||
#endif
|
||||
int major = 0, minor = 0, micro = 0;
|
||||
wxGetOsVersion(&major, &minor, µ);
|
||||
std::string os_version = (boost::format("%1%.%2%.%3%") % major % minor % micro).str();
|
||||
extra_headers.insert(std::make_pair("X-BBL-OS-Version", os_version));
|
||||
if (app_config)
|
||||
extra_headers.insert(std::make_pair("X-BBL-Device-ID", app_config->get("slicer_uuid")));
|
||||
extra_headers.insert(std::make_pair("X-BBL-Language", convert_studio_language_to_api(into_u8(current_language_code_safe()))));
|
||||
return extra_headers;
|
||||
}
|
||||
|
||||
std::string GUI_App::get_bbl_client_version()
|
||||
{
|
||||
if (BBLNetworkPlugin::instance().get_get_my_token() == nullptr) {
|
||||
// Legacy Bambu plugin lacks bambu_network_get_my_token. Pin client
|
||||
// version so the auth server keeps using the ?access_token= redirect.
|
||||
return "01.10.01.50";
|
||||
}
|
||||
return VersionInfo::convert_full_version(SLIC3R_VERSION);
|
||||
}
|
||||
|
||||
//BBS
|
||||
void GUI_App::init_http_extra_header()
|
||||
{
|
||||
std::map<std::string, std::string> extra_headers = get_extra_header();
|
||||
|
||||
if (m_agent)
|
||||
m_agent->set_extra_http_header(extra_headers);
|
||||
}
|
||||
|
||||
void GUI_App::update_http_extra_header()
|
||||
{
|
||||
std::map<std::string, std::string> extra_headers = get_extra_header();
|
||||
Slic3r::Http::set_extra_headers(extra_headers);
|
||||
if (m_agent)
|
||||
m_agent->set_extra_http_header(extra_headers);
|
||||
}
|
||||
|
||||
void GUI_App::on_start_subscribe_again(std::string dev_id)
|
||||
{
|
||||
auto start_subscribe_timer = new wxTimer(this, wxID_ANY);
|
||||
@@ -2608,6 +2548,7 @@ bool GUI_App::OnInit()
|
||||
|
||||
int GUI_App::OnExit()
|
||||
{
|
||||
stop_http_server();
|
||||
stop_sync_user_preset();
|
||||
|
||||
if (m_device_manager) {
|
||||
@@ -3059,8 +3000,6 @@ bool GUI_App::on_init_inner()
|
||||
Bind(EVT_SHOW_IP_DIALOG, &GUI_App::show_ip_address_enter_dialog_handler, this);
|
||||
|
||||
|
||||
std::map<std::string, std::string> extra_headers = get_extra_header();
|
||||
Slic3r::Http::set_extra_headers(extra_headers);
|
||||
|
||||
// Orca: select network plugin version based on configured version string
|
||||
std::string configured_version = app_config->get_network_plugin_version();
|
||||
@@ -3481,8 +3420,6 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
if (m_agent)
|
||||
m_agent->set_cert_file(resources_dir() + "/cert", "slicer_base64.cer");
|
||||
|
||||
init_http_extra_header();
|
||||
|
||||
if (m_agent) {
|
||||
init_networking_callbacks();
|
||||
std::string country_code = app_config->get_country_code();
|
||||
@@ -3490,6 +3427,24 @@ bool GUI_App::on_init_network(bool try_backup)
|
||||
m_agent->start();
|
||||
}
|
||||
|
||||
// When using Orca cloud alongside the BBL network plugin, the BBL DLL agent still
|
||||
// needs to be created and configured (config dir, certs, country, start) so that
|
||||
// BBLPrinterAgent can use it for LAN discovery and printer communication.
|
||||
if (should_load_networking_plugin && !m_networking_need_update) {
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
if (plugin.is_loaded() && !plugin.has_agent()) {
|
||||
plugin.create_agent(data_directory);
|
||||
}
|
||||
if (plugin.has_agent()) {
|
||||
BBLCloudServiceAgent bbl;
|
||||
bbl.set_config_dir(data_directory);
|
||||
bbl.init_log();
|
||||
bbl.set_cert_file(resources_dir() + "/cert", "slicer_base64.cer");
|
||||
bbl.set_country_code(app_config->get_country_code());
|
||||
bbl.start();
|
||||
}
|
||||
}
|
||||
|
||||
if (!should_load_networking_plugin) {
|
||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, unload_network_module, result = " << result;
|
||||
@@ -3544,8 +3499,10 @@ void GUI_App::switch_printer_agent()
|
||||
|
||||
// Read printer_agent from config, falling back to default
|
||||
std::string effective_agent_id = ORCA_PRINTER_AGENT_ID;
|
||||
std::string cloud_agent_id = ORCA_CLOUD_PROVIDER;
|
||||
if (preset_bundle->is_bbl_vendor()) {
|
||||
effective_agent_id = BBL_PRINTER_AGENT_ID;
|
||||
cloud_agent_id = BBL_CLOUD_PROVIDER;
|
||||
} else {
|
||||
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printer_agent")) {
|
||||
@@ -3569,7 +3526,7 @@ void GUI_App::switch_printer_agent()
|
||||
|
||||
if (current_agent_id != effective_agent_id) {
|
||||
std::string log_dir = data_dir();
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent();
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
|
||||
|
||||
// Create new printer agent via registry
|
||||
std::shared_ptr<IPrinterAgent> new_printer_agent =
|
||||
@@ -4139,7 +4096,6 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI enter";
|
||||
m_is_recreating_gui = true;
|
||||
|
||||
update_http_extra_header();
|
||||
|
||||
mainframe->shutdown();
|
||||
ProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE);
|
||||
@@ -4243,17 +4199,14 @@ void GUI_App::ShowDownNetPluginDlg() {
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::ShowUserLogin(bool show)
|
||||
void GUI_App::ShowUserLogin(bool show, const std::string& provider)
|
||||
{
|
||||
// BBS: User Login Dialog
|
||||
// Show user Login Dialog for specified cloud
|
||||
if (show) {
|
||||
try {
|
||||
if (!login_dlg)
|
||||
login_dlg = new ZUserLogin();
|
||||
else {
|
||||
delete login_dlg;
|
||||
login_dlg = new ZUserLogin();
|
||||
}
|
||||
delete login_dlg;
|
||||
auto cloud_agent = m_agent->get_cloud_agent(provider);
|
||||
login_dlg = new ZUserLogin(cloud_agent);
|
||||
login_dlg->ShowModal();
|
||||
} catch (std::exception &) {
|
||||
;
|
||||
@@ -4481,100 +4434,120 @@ wxString GUI_App::transition_tridid(int trid_id) const
|
||||
}
|
||||
|
||||
//BBS
|
||||
void GUI_App::request_login(bool show_user_info)
|
||||
void GUI_App::request_login(bool show_user_info, const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
ShowUserLogin();
|
||||
ShowUserLogin(true, provider);
|
||||
|
||||
if (show_user_info) {
|
||||
get_login_info();
|
||||
CallAfter([this, provider] {
|
||||
get_login_info(provider);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::get_login_info()
|
||||
void GUI_App::get_login_info(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
if (m_agent) {
|
||||
if (m_agent->is_user_login()) {
|
||||
std::string login_cmd = m_agent->build_login_cmd();
|
||||
if (m_agent->is_user_login(provider)) {
|
||||
std::string login_cmd = m_agent->build_login_cmd(provider);
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", login_cmd);
|
||||
GUI::wxGetApp().run_script(strJS);
|
||||
}
|
||||
else {
|
||||
// OrcaNetwork performs async refresh on startup; avoid clearing
|
||||
// persisted tokens when the UI polls before refresh completes.
|
||||
if (m_agent->get_version() != "orca_network") {
|
||||
m_agent->user_logout();
|
||||
std::string logout_cmd = m_agent->build_logout_cmd();
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", logout_cmd);
|
||||
GUI::wxGetApp().run_script(strJS);
|
||||
}
|
||||
}
|
||||
if(app_config->get_bool("installed_networking")) {
|
||||
mainframe->m_webview->SetLoginPanelVisibility(true);
|
||||
} else {
|
||||
mainframe->m_webview->SetLoginPanelVisibility(false);
|
||||
m_agent->user_logout(false, provider);
|
||||
std::string logout_cmd = m_agent->build_logout_cmd(provider);
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", logout_cmd);
|
||||
GUI::wxGetApp().run_script(strJS);
|
||||
}
|
||||
mainframe->m_webview->SetLoginPanelVisibility(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool GUI_App::is_user_login()
|
||||
bool GUI_App::is_user_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
if (m_agent) {
|
||||
return m_agent->is_user_login();
|
||||
return m_agent->is_user_login(provider);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& GUI_App::get_printer_cloud_provider() const
|
||||
{
|
||||
// Orca todo: this need to be revisted. currently it is mainly used for device manager and related clausses and only bambu machines use them.
|
||||
//
|
||||
return BBL_CLOUD_PROVIDER;
|
||||
}
|
||||
|
||||
bool GUI_App::check_login()
|
||||
|
||||
bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
bool result = false;
|
||||
if (m_agent) {
|
||||
result = m_agent->is_user_login();
|
||||
result = m_agent->is_user_login(provider);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
ShowUserLogin();
|
||||
ShowUserLogin(true, provider);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void GUI_App::request_user_handle(int online_login)
|
||||
void GUI_App::request_user_handle(int online_login, const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_USER_LOGIN_HANDLE);
|
||||
evt->SetInt(online_login);
|
||||
evt->SetString(wxString::FromUTF8(provider));
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void GUI_App::request_user_login(int online_login)
|
||||
void GUI_App::request_user_login(int online_login, const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_USER_LOGIN);
|
||||
evt->SetInt(online_login);
|
||||
evt->SetString(wxString::FromUTF8(provider));
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void GUI_App::request_user_logout()
|
||||
void GUI_App::post_logout_to_webview(const std::string& provider)
|
||||
{
|
||||
if (m_agent && m_agent->is_user_login()) {
|
||||
// Update data first before showing dialogs
|
||||
m_agent->user_logout(true);
|
||||
m_agent->set_user_selected_machine("");
|
||||
/* delete old user settings */
|
||||
bool transfer_preset_changes = false;
|
||||
wxString header = _L("Some presets are modified.") + "\n" +
|
||||
_L("You can keep the modified presets to the new project, discard or save changes as new presets.");
|
||||
wxGetApp().check_and_keep_current_preset_changes(_L("User logged out"), header, ActionButtons::KEEP | ActionButtons::SAVE, &transfer_preset_changes);
|
||||
|
||||
m_device_manager->clean_user_info();
|
||||
remove_user_presets();
|
||||
enable_user_preset_folder(false);
|
||||
preset_bundle->load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
mainframe->update_side_preset_ui();
|
||||
|
||||
GUI::wxGetApp().stop_sync_user_preset();
|
||||
std::string logout_cmd = m_agent->build_logout_cmd(provider);
|
||||
if (!logout_cmd.empty()) {
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", logout_cmd);
|
||||
GUI::wxGetApp().run_script(strJS);
|
||||
}
|
||||
}
|
||||
|
||||
int GUI_App::request_user_unbind(std::string dev_id)
|
||||
void GUI_App::request_user_logout(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
if (m_agent && m_agent->is_user_login(provider)) {
|
||||
m_agent->user_logout(true, provider);
|
||||
|
||||
if (provider == get_printer_cloud_provider()) {
|
||||
m_agent->set_user_selected_machine("");
|
||||
if (m_device_manager) {
|
||||
m_device_manager->clean_user_info(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (provider == ORCA_CLOUD_PROVIDER) {
|
||||
/* delete old user settings */
|
||||
bool transfer_preset_changes = false;
|
||||
wxString header = _L("Some presets are modified.") + "\n" +
|
||||
_L("You can keep the modified presets to the new project, discard or save changes as new presets.");
|
||||
wxGetApp().check_and_keep_current_preset_changes(_L("User logged out"), header, ActionButtons::KEEP | ActionButtons::SAVE, &transfer_preset_changes);
|
||||
|
||||
remove_user_presets();
|
||||
enable_user_preset_folder(false);
|
||||
preset_bundle->load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
mainframe->update_side_preset_ui();
|
||||
|
||||
GUI::wxGetApp().stop_sync_user_preset();
|
||||
}
|
||||
|
||||
post_logout_to_webview(provider);
|
||||
}
|
||||
}
|
||||
|
||||
int GUI_App::request_user_unbind(std::string dev_id, const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
|
||||
{
|
||||
int result = -1;
|
||||
if (m_agent) {
|
||||
@@ -4632,6 +4605,24 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
wxGetApp().request_user_logout();
|
||||
});
|
||||
}
|
||||
else if (command_str.compare("get_orca_login_info") == 0) {
|
||||
CallAfter([this] { get_login_info(ORCA_CLOUD_PROVIDER); });
|
||||
}
|
||||
else if (command_str.compare("get_bambu_login_info") == 0) {
|
||||
CallAfter([this] { get_login_info(BBL_CLOUD_PROVIDER); });
|
||||
}
|
||||
else if (command_str.compare("homepage_bambu_login_or_register") == 0) {
|
||||
CallAfter([this] { request_login(true, BBL_CLOUD_PROVIDER); });
|
||||
}
|
||||
else if (command_str.compare("homepage_bambu_logout") == 0) {
|
||||
CallAfter([this] { request_user_logout(BBL_CLOUD_PROVIDER); });
|
||||
}
|
||||
else if (command_str.compare("homepage_orca_login_or_register") == 0) {
|
||||
CallAfter([this] { request_login(true, ORCA_CLOUD_PROVIDER); });
|
||||
}
|
||||
else if (command_str.compare("homepage_orca_logout") == 0) {
|
||||
CallAfter([this] { request_user_logout(ORCA_CLOUD_PROVIDER); });
|
||||
}
|
||||
else if (command_str.compare("homepage_modeldepot") == 0) {
|
||||
CallAfter([this] {
|
||||
wxGetApp().open_mall_page_dialog();
|
||||
@@ -4784,7 +4775,7 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
return "";
|
||||
}
|
||||
|
||||
void GUI_App::handle_script_message(std::string msg)
|
||||
void GUI_App::handle_script_message(std::string msg, const std::string& provider)
|
||||
{
|
||||
try {
|
||||
json j = json::parse(msg);
|
||||
@@ -4792,9 +4783,9 @@ void GUI_App::handle_script_message(std::string msg)
|
||||
wxString cmd = j["command"];
|
||||
if (cmd == "user_login") {
|
||||
if (m_agent) {
|
||||
m_agent->change_user(j.dump());
|
||||
if (m_agent->is_user_login()) {
|
||||
request_user_login(1);
|
||||
m_agent->change_user(j.dump(), provider);
|
||||
if (m_agent->is_user_login(provider)) {
|
||||
request_user_login(1, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4852,35 +4843,51 @@ void GUI_App::request_remove_project(std::string project_id)
|
||||
mainframe->remove_recent_project(-1, wxString::FromUTF8(project_id));
|
||||
}
|
||||
|
||||
void GUI_App::handle_http_error(unsigned int status, std::string body)
|
||||
void GUI_App::handle_http_error(unsigned int status, std::string body, const std::string& provider)
|
||||
{
|
||||
// tips body size must less than 1024
|
||||
auto evt = new wxCommandEvent(EVT_HTTP_ERROR);
|
||||
evt->SetInt(status);
|
||||
evt->SetString(wxString(body));
|
||||
// Encode provider into the event string alongside body
|
||||
json evt_data;
|
||||
evt_data["body"] = body;
|
||||
evt_data["provider"] = provider;
|
||||
evt->SetString(wxString::FromUTF8(evt_data.dump()));
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
void GUI_App::on_http_error(wxCommandEvent &evt)
|
||||
{
|
||||
int status = evt.GetInt();
|
||||
std::string provider = ORCA_CLOUD_PROVIDER;
|
||||
std::string body_str;
|
||||
|
||||
// Extract provider and body from event data
|
||||
try {
|
||||
auto evt_str = evt.GetString().utf8_string();
|
||||
if (!evt_str.empty()) {
|
||||
json evt_data = json::parse(evt_str);
|
||||
if (evt_data.contains("provider"))
|
||||
provider = evt_data["provider"].get<std::string>();
|
||||
if (evt_data.contains("body"))
|
||||
body_str = evt_data["body"].get<std::string>();
|
||||
}
|
||||
} catch (...) {}
|
||||
|
||||
int code = 0;
|
||||
std::string error;
|
||||
wxString result;
|
||||
if (status >= 400 && status < 500) {
|
||||
try {
|
||||
auto evt_str = evt.GetString();
|
||||
if (!evt_str.empty()) {
|
||||
json j = json::parse(evt_str.utf8_string());
|
||||
if (j.contains("code")) {
|
||||
if (!j["code"].is_null())
|
||||
code = j["code"].get<int>();
|
||||
if (!body_str.empty()) {
|
||||
json j = json::parse(body_str);
|
||||
if (j.contains("code")) {
|
||||
if (!j["code"].is_null())
|
||||
code = j["code"].get<int>();
|
||||
}
|
||||
if (j.contains("error"))
|
||||
if (!j["error"].is_null())
|
||||
error = j["error"].get<std::string>();
|
||||
}
|
||||
if (j.contains("error"))
|
||||
if (!j["error"].is_null())
|
||||
error = j["error"].get<std::string>();
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
@@ -4889,14 +4896,13 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
|
||||
MessageDialog msg_dlg(nullptr, _L("The version of Orca Slicer is too low and needs to be updated to the latest version before it can be used normally."), "", wxAPPLY | wxOK);
|
||||
if (msg_dlg.ShowModal() == wxOK) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// request login
|
||||
if (status == 401) {
|
||||
if (m_agent) {
|
||||
if (m_agent->is_user_login()) {
|
||||
this->request_user_logout();
|
||||
if (m_agent->is_user_login(provider)) {
|
||||
this->request_user_logout(provider);
|
||||
|
||||
if (!m_show_http_errpr_msgdlg) {
|
||||
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
|
||||
@@ -4943,23 +4949,33 @@ void GUI_App::on_user_login_handle(wxCommandEvent &evt)
|
||||
}
|
||||
|
||||
int online_login = evt.GetInt();
|
||||
std::string provider = evt.GetString().ToStdString();
|
||||
if (provider.empty()) provider = ORCA_CLOUD_PROVIDER;
|
||||
|
||||
m_agent->connect_server();
|
||||
// get machine list
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return;
|
||||
|
||||
boost::thread update_thread = boost::thread([this, dev] {
|
||||
dev->update_user_machine_list_info();
|
||||
boost::thread update_thread = boost::thread([dev, provider] {
|
||||
dev->update_user_machine_list_info(provider);
|
||||
});
|
||||
|
||||
if (online_login) {
|
||||
if (online_login && provider == ORCA_CLOUD_PROVIDER) {
|
||||
maybe_migrate_user_presets_on_login();
|
||||
remove_user_presets();
|
||||
enable_user_preset_folder(true);
|
||||
preset_bundle->load_user_presets(m_agent->get_user_id(), ForwardCompatibilitySubstitutionRule::Enable);
|
||||
preset_bundle->load_user_presets(m_agent->get_user_id(provider), ForwardCompatibilitySubstitutionRule::Enable);
|
||||
mainframe->update_side_preset_ui();
|
||||
|
||||
GUI::wxGetApp().mainframe->show_sync_dialog();
|
||||
}
|
||||
|
||||
// Ensure sync thread starts after login completes (regardless of login type).
|
||||
// Safe: start_sync_user_preset() has a dedup guard (m_user_sync_token) to prevent duplicate threads.
|
||||
if (app_config->get("sync_user_preset") == "true") {
|
||||
start_sync_user_preset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4976,8 +4992,11 @@ void GUI_App::on_user_login(wxCommandEvent &evt)
|
||||
{
|
||||
if (!m_agent) { return; }
|
||||
int online_login = evt.GetInt();
|
||||
std::string provider = evt.GetString().ToStdString();
|
||||
if (provider.empty()) provider = ORCA_CLOUD_PROVIDER;
|
||||
|
||||
// check privacy before handle
|
||||
check_privacy_version(online_login);
|
||||
check_privacy_version(online_login, provider);
|
||||
check_track_enable();
|
||||
}
|
||||
|
||||
@@ -5645,16 +5664,19 @@ void GUI_App::set_skip_version(bool skip)
|
||||
void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt)
|
||||
{
|
||||
int online_login = evt.GetInt();
|
||||
std::string provider = evt.GetString().ToStdString();
|
||||
if (provider.empty()) provider = ORCA_CLOUD_PROVIDER;
|
||||
PrivacyUpdateDialog privacy_dlg(this->mainframe, wxID_ANY, _L("Privacy Policy Update"));
|
||||
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CONFIRM, [this, online_login](wxCommandEvent &e) {
|
||||
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CONFIRM, [this, online_login, provider](wxCommandEvent &e) {
|
||||
app_config->set("privacy_version", privacy_version_info.version_str);
|
||||
app_config->set_bool("privacy_update_checked", true);
|
||||
request_user_handle(online_login);
|
||||
request_user_handle(online_login, provider);
|
||||
});
|
||||
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this](wxCommandEvent &e) {
|
||||
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this, provider](wxCommandEvent &e) {
|
||||
app_config->set_bool("privacy_update_checked", false);
|
||||
if (m_agent) {
|
||||
m_agent->user_logout();
|
||||
m_agent->user_logout(false, provider);
|
||||
post_logout_to_webview(provider);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5662,10 +5684,11 @@ void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt)
|
||||
privacy_dlg.on_show();
|
||||
}
|
||||
|
||||
void GUI_App::on_show_check_privacy_dlg(int online_login)
|
||||
void GUI_App::on_show_check_privacy_dlg(int online_login, const std::string& provider)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_CHECK_PRIVACY_SHOW);
|
||||
evt->SetInt(online_login);
|
||||
evt->SetString(wxString::FromUTF8(provider));
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
@@ -5690,21 +5713,22 @@ bool GUI_App::check_privacy_update()
|
||||
void GUI_App::on_check_privacy_update(wxCommandEvent& evt)
|
||||
{
|
||||
int online_login = evt.GetInt();
|
||||
std::string provider = evt.GetString().ToStdString();
|
||||
if (provider.empty()) provider = ORCA_CLOUD_PROVIDER;
|
||||
bool result = check_privacy_update();
|
||||
if (result)
|
||||
on_show_check_privacy_dlg(online_login);
|
||||
on_show_check_privacy_dlg(online_login, provider);
|
||||
else
|
||||
request_user_handle(online_login);
|
||||
request_user_handle(online_login, provider);
|
||||
}
|
||||
|
||||
void GUI_App::check_privacy_version(int online_login)
|
||||
void GUI_App::check_privacy_version(int online_login, const std::string& provider)
|
||||
{
|
||||
if (app_config->get_stealth_mode()) {
|
||||
request_user_handle(online_login);
|
||||
return;
|
||||
}
|
||||
|
||||
update_http_extra_header();
|
||||
std::string query_params = "?policy/privacy=00.00.00.00";
|
||||
std::string url = get_http_url(app_config->get_country_code()) + query_params;
|
||||
Slic3r::Http http = Slic3r::Http::get(url);
|
||||
@@ -5712,7 +5736,7 @@ void GUI_App::check_privacy_version(int online_login)
|
||||
http.header("accept", "application/json")
|
||||
.timeout_connect(TIMEOUT_CONNECT)
|
||||
.timeout_max(TIMEOUT_RESPONSE)
|
||||
.on_complete([this, online_login](std::string body, unsigned) {
|
||||
.on_complete([this, online_login, provider](std::string body, unsigned) {
|
||||
try {
|
||||
json j = json::parse(body);
|
||||
if (j.contains("message")) {
|
||||
@@ -5733,9 +5757,10 @@ void GUI_App::check_privacy_version(int online_login)
|
||||
}
|
||||
}
|
||||
}
|
||||
CallAfter([this, online_login]() {
|
||||
CallAfter([this, online_login, provider]() {
|
||||
auto evt = new wxCommandEvent(EVT_CHECK_PRIVACY_VER);
|
||||
evt->SetInt(online_login);
|
||||
evt->SetString(wxString::FromUTF8(provider));
|
||||
wxQueueEvent(this, evt);
|
||||
});
|
||||
}
|
||||
@@ -5743,11 +5768,11 @@ void GUI_App::check_privacy_version(int online_login)
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
request_user_handle(online_login);
|
||||
request_user_handle(online_login, provider);
|
||||
}
|
||||
})
|
||||
.on_error([this, online_login](std::string body, std::string error, unsigned int status) {
|
||||
request_user_handle(online_login);
|
||||
.on_error([this, online_login, provider](std::string body, std::string error, unsigned int status) {
|
||||
request_user_handle(online_login, provider);
|
||||
BOOST_LOG_TRIVIAL(error) << "check privacy version error" << body;
|
||||
}).perform();
|
||||
}
|
||||
@@ -5828,9 +5853,20 @@ void GUI_App::push_notification(const MachineObject* obj, wxString msg, wxStrin
|
||||
void GUI_App::reload_settings()
|
||||
{
|
||||
if (preset_bundle && m_agent) {
|
||||
// Load user's personal presets
|
||||
std::map<std::string, std::map<std::string, std::string>> user_presets;
|
||||
m_agent->get_user_presets(&user_presets);
|
||||
int result = m_agent->get_user_presets(&user_presets);
|
||||
if (result != 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get_user_presets failed with code " << result << ", skipping sync";
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " cloud user preset number is: " << user_presets.size();
|
||||
// Check the user presets for any system vendors that need to be installed
|
||||
for (auto data : user_presets) {
|
||||
if (!check_preset_parent_available(data))
|
||||
add_pending_vendor_preset(data);
|
||||
}
|
||||
load_pending_vendors();
|
||||
preset_bundle->load_user_presets(*app_config, user_presets, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
preset_bundle->save_user_presets(*app_config, get_delete_cache_presets());
|
||||
mainframe->update_side_preset_ui();
|
||||
@@ -5852,6 +5888,230 @@ void GUI_App::remove_user_presets()
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user's OrcaCloud profile directory is empty and offer to migrate
|
||||
// existing profiles from the default or BambuCloud user folder.
|
||||
// Returns true if migration was performed, false otherwise.
|
||||
bool GUI_App::maybe_migrate_user_presets_on_login()
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
if (!m_agent || !m_agent->is_user_login())
|
||||
return false;
|
||||
|
||||
std::string new_user_id = m_agent->get_user_id();
|
||||
if (new_user_id.empty())
|
||||
return false;
|
||||
|
||||
fs::path user_base = fs::path(data_dir()) / PRESET_USER_DIR;
|
||||
fs::path target_dir = user_base / new_user_id;
|
||||
|
||||
// Check if the user already has presets on OrcaCloud.
|
||||
// We must query the cloud (not the local folder) to avoid overwriting existing cloud profiles
|
||||
// that haven't been synced down yet (e.g. fresh install with existing cloud account).
|
||||
{
|
||||
std::map<std::string, std::map<std::string, std::string>> cloud_presets;
|
||||
int ret = m_agent->get_user_presets(&cloud_presets);
|
||||
if (ret == 0 && !cloud_presets.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "OrcaCloud already has " << cloud_presets.size()
|
||||
<< " presets, skipping migration for user: " << new_user_id;
|
||||
return false;
|
||||
}
|
||||
if (ret != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to query OrcaCloud presets (error " << ret
|
||||
<< "), skipping migration to avoid overwriting cloud data.";
|
||||
return false;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "OrcaCloud has no presets for user " << new_user_id << ", proceeding with migration check.";
|
||||
}
|
||||
|
||||
// Helper to check if a local directory has any .json preset files.
|
||||
auto has_json_presets = [](const fs::path& dir) -> bool {
|
||||
try {
|
||||
if (!fs::exists(dir) || !fs::is_directory(dir))
|
||||
return false;
|
||||
boost::system::error_code ec;
|
||||
for (auto it = fs::recursive_directory_iterator(dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Error scanning directory " << dir << ": " << ec.message();
|
||||
continue;
|
||||
}
|
||||
if (fs::is_regular_file(*it) && it->path().extension() == ".json")
|
||||
return true;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to scan directory for presets: " << e.what();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Determine the source directory to migrate from.
|
||||
// Priority: 1) Bambu Cloud user folder (if user was logged in), 2) "default" folder, 3) any other user-ID folder
|
||||
fs::path source_dir;
|
||||
bool source_is_default = false;
|
||||
bool source_is_bbl = false;
|
||||
fs::path default_dir = user_base / DEFAULT_USER_FOLDER_NAME;
|
||||
|
||||
// Check if the user was previously logged into Bambu Cloud and has presets there
|
||||
if (m_agent->is_user_login(BBL_CLOUD_PROVIDER)) {
|
||||
std::string bbl_user_id = m_agent->get_user_id(BBL_CLOUD_PROVIDER);
|
||||
if (!bbl_user_id.empty() && bbl_user_id != new_user_id) {
|
||||
fs::path bbl_dir = user_base / bbl_user_id;
|
||||
if (has_json_presets(bbl_dir)) {
|
||||
source_dir = bbl_dir;
|
||||
source_is_bbl = true;
|
||||
BOOST_LOG_TRIVIAL(info) << "Migration source: Bambu Cloud user folder: " << source_dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default folder
|
||||
if (source_dir.empty() && has_json_presets(default_dir)) {
|
||||
source_dir = default_dir;
|
||||
source_is_default = true;
|
||||
BOOST_LOG_TRIVIAL(info) << "Migration source: default user folder: " << source_dir;
|
||||
}
|
||||
|
||||
// Last resort: scan for any other user-ID folder with presets
|
||||
if (source_dir.empty() && fs::exists(user_base) && fs::is_directory(user_base)) {
|
||||
for (auto& entry : fs::directory_iterator(user_base)) {
|
||||
if (!fs::is_directory(entry))
|
||||
continue;
|
||||
std::string folder_name = entry.path().filename().string();
|
||||
if (folder_name == new_user_id || folder_name == DEFAULT_USER_FOLDER_NAME)
|
||||
continue;
|
||||
if (has_json_presets(entry.path())) {
|
||||
source_dir = entry.path();
|
||||
BOOST_LOG_TRIVIAL(info) << "Migration source: user folder: " << source_dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (source_dir.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "No existing user presets found to migrate.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ask the user for confirmation with a message tailored to the source type
|
||||
wxString source_description;
|
||||
if (source_is_bbl) {
|
||||
source_description = wxString::Format(
|
||||
_L("your Bambu Cloud profile (user ID: \"%s\")"),
|
||||
from_u8(source_dir.filename().string()));
|
||||
} else if (source_is_default) {
|
||||
source_description = _L("your default profile");
|
||||
} else {
|
||||
source_description = wxString::Format(
|
||||
_L("a user profile (folder: \"%s\")"),
|
||||
from_u8(source_dir.filename().string()));
|
||||
}
|
||||
|
||||
wxString msg = wxString::Format(
|
||||
_L("Existing user presets were found in %s.\n"
|
||||
"Do you want to migrate them to your OrcaCloud profile?\n"
|
||||
"This will copy your presets so they are available under your new account."),
|
||||
source_description);
|
||||
|
||||
MessageDialog dlg(mainframe, msg, _L("Migrate User Presets"),
|
||||
wxCENTER | wxYES_DEFAULT | wxYES_NO | wxICON_INFORMATION);
|
||||
if (dlg.ShowModal() != wxID_YES) {
|
||||
BOOST_LOG_TRIVIAL(info) << "User declined preset migration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform the migration using copy_directory_recursively in merge mode
|
||||
// to preserve any existing files (e.g. .info sync markers) in the target directory.
|
||||
try {
|
||||
wxBusyCursor busy;
|
||||
BOOST_LOG_TRIVIAL(info) << "Migrating user presets from " << source_dir << " to " << target_dir;
|
||||
|
||||
auto info_filter = [](const std::string& filename) -> bool {
|
||||
// Return true to skip .info files
|
||||
return filename.size() >= 5 &&
|
||||
filename.compare(filename.size() - 5, 5, ".info") == 0;
|
||||
};
|
||||
|
||||
copy_directory_recursively(source_dir, target_dir, info_filter, /*merge_mode=*/true);
|
||||
BOOST_LOG_TRIVIAL(info) << "User preset migration completed successfully.";
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to migrate user presets: " << ex.what();
|
||||
wxString err_msg = wxString::Format(
|
||||
_L("Failed to migrate user presets:\n%s"),
|
||||
from_u8(ex.what()));
|
||||
show_error(nullptr, err_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GUI_App::check_preset_parent_available(const std::pair<std::string, std::map<std::string, std::string>>& preset_data)
|
||||
{
|
||||
std::string inherits_name = preset_data.second.at(BBL_JSON_KEY_INHERITS);
|
||||
// // If contains "fdm_", "@System", and "@base", is a common base template that doesn't need to be installed
|
||||
if (inherits_name.find("fdm_") != std::string::npos || inherits_name.find("@System") != std::string::npos || inherits_name.find("@base") != std::string::npos)
|
||||
return true;
|
||||
|
||||
if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_PRINT_TYPE)
|
||||
return preset_bundle->prints.find_preset2(inherits_name) != nullptr;
|
||||
else if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_PRINTER_TYPE)
|
||||
return preset_bundle->printers.find_preset2(inherits_name) != nullptr;
|
||||
else if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_FILAMENT_TYPE)
|
||||
return preset_bundle->filaments.find_preset2(inherits_name) != nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GUI_App::add_pending_vendor_preset(const std::pair<std::string, std::map<std::string, std::string>>& preset_data)
|
||||
{
|
||||
Preset::Type type;
|
||||
if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_PRINT_TYPE)
|
||||
type = Preset::Type::TYPE_PRINT;
|
||||
else if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_PRINTER_TYPE)
|
||||
type = Preset::Type::TYPE_PRINTER;
|
||||
else if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_FILAMENT_TYPE)
|
||||
type = Preset::Type::TYPE_FILAMENT;
|
||||
std::string inherits_name = preset_data.second.at(BBL_JSON_KEY_INHERITS);
|
||||
|
||||
// Add the corresponding vendor
|
||||
std::string vendor_name = PresetBundle::find_preset_vendor(inherits_name, type);
|
||||
if (need_add_vendors.find(vendor_name) == need_add_vendors.end())
|
||||
need_add_vendors[vendor_name] = std::map<std::string, std::set<std::string>>();
|
||||
|
||||
// Add printers/filament if applicable
|
||||
if (type == Preset::Type::TYPE_PRINTER) {
|
||||
// Extract float from preset name if present
|
||||
std::string model_name = inherits_name;
|
||||
std::regex float_regex(R"((\b\d+\.\d+))");
|
||||
std::smatch match;
|
||||
if (std::regex_search(model_name, match, float_regex) && match.size() > 1) {
|
||||
// Get variant i.e., nozzle diameter
|
||||
std::string nozzle_diameter = match[1].str();
|
||||
// Get model name
|
||||
model_name.erase(model_name.find(nozzle_diameter));
|
||||
model_name.erase(model_name.rfind(' '));
|
||||
if(need_add_vendors[vendor_name].find(model_name) == need_add_vendors[vendor_name].end())
|
||||
need_add_vendors[vendor_name][model_name] = std::set<std::string>();
|
||||
|
||||
need_add_vendors[vendor_name][model_name].insert(nozzle_diameter);
|
||||
}
|
||||
}
|
||||
else if (type == Preset::Type::TYPE_FILAMENT) {
|
||||
need_add_filaments[inherits_name] = "true";
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::load_pending_vendors()
|
||||
{
|
||||
if (need_add_vendors.size() == 0 && need_add_filaments.size() == 0)
|
||||
return;
|
||||
|
||||
preset_bundle->apply_vendor_config(need_add_vendors, need_add_filaments, app_config, false);
|
||||
app_config->save();
|
||||
|
||||
need_add_vendors.clear();
|
||||
need_add_filaments.clear();
|
||||
}
|
||||
|
||||
void GUI_App::sync_preset(Preset* preset)
|
||||
{
|
||||
int result = -1;
|
||||
@@ -5863,7 +6123,12 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
|
||||
auto setting_id = preset->setting_id;
|
||||
std::map<std::string, std::string> values_map;
|
||||
if (setting_id.empty() && preset->sync_info.empty()) {
|
||||
|
||||
// Check and catch if the file is new and missing .info
|
||||
bool needs_init = setting_id.empty() && preset->sync_info.empty();
|
||||
|
||||
// Actually process sync info
|
||||
if (needs_init || preset->sync_info.compare("create") == 0) {
|
||||
if (m_create_preset_blocked[preset->type])
|
||||
return;
|
||||
int ret = preset_bundle->get_differed_values_to_update(*preset, values_map);
|
||||
@@ -5911,8 +6176,6 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
result = 0;
|
||||
updated_info = "hold";
|
||||
}
|
||||
else
|
||||
result = -1;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(trace) << "[sync_preset]create: can not generate differed preset";
|
||||
@@ -5927,16 +6190,16 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
result = 0;
|
||||
}
|
||||
else {
|
||||
result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code);
|
||||
if (http_code >= 400) {
|
||||
result = 0;
|
||||
updated_info = "hold";
|
||||
BOOST_LOG_TRIVIAL(error) << "[sync_preset] put setting_id = " << setting_id << " failed, http_code = " << http_code;
|
||||
} else {
|
||||
auto update_time_str = values_map[ORCA_JSON_KEY_UPDATE_TIME];
|
||||
if (!update_time_str.empty())
|
||||
update_time = std::atoll(update_time_str.c_str());
|
||||
}
|
||||
result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code);
|
||||
if (http_code >= 400) {
|
||||
result = 0;
|
||||
updated_info = "hold";
|
||||
BOOST_LOG_TRIVIAL(error) << "[sync_preset] put setting_id = " << setting_id << " failed, http_code = " << http_code;
|
||||
} else {
|
||||
auto update_time_str = values_map[ORCA_JSON_KEY_UPDATE_TIME];
|
||||
if (!update_time_str.empty())
|
||||
update_time = std::atoll(update_time_str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5967,6 +6230,26 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
return; // this error not need hold, and should not hold
|
||||
}
|
||||
|
||||
// Handle HTTP 413 - Payload Too Large
|
||||
if (http_code == 413) {
|
||||
// Set sync_info to "will_not_sync" so this preset won't be synced again
|
||||
updated_info = "will_not_sync";
|
||||
result = 0; // Set to 0 so the sync_info gets saved below
|
||||
|
||||
// Show user notification
|
||||
CallAfter([this] {
|
||||
static bool size_limit_dialog_notified = false;
|
||||
if (size_limit_dialog_notified)
|
||||
return;
|
||||
size_limit_dialog_notified = true;
|
||||
if (mainframe == nullptr)
|
||||
return;
|
||||
auto msg = _L("The preset content is too large to sync to the cloud (exceeds 1MB). Please reduce the preset size by removing custom configurations or use it locally only.");
|
||||
MessageDialog(mainframe, msg, _L("Sync user presets"), wxICON_WARNING | wxOK).ShowModal();
|
||||
});
|
||||
// NOTE: Don't return here - let execution continue to save the sync_info
|
||||
}
|
||||
|
||||
// update sync_info preset info in file
|
||||
if (result == 0) {
|
||||
//PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
@@ -5983,6 +6266,300 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::update_single_bundle(wxCommandEvent& evt)
|
||||
{
|
||||
if (!m_agent || !m_agent->is_user_login()) return;
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
|
||||
if (!orca_agent) return;
|
||||
|
||||
const std::string bundle_id = evt.GetString().ToStdString();
|
||||
|
||||
// Fetch the latest bundle data from cloud
|
||||
std::map<std::string, std::map<std::string, std::string>> bundle_presets;
|
||||
BundleMetadata remote_metadata;
|
||||
int result = orca_agent->get_shared_bundle(bundle_id, &bundle_presets, &remote_metadata);
|
||||
|
||||
if (result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "sync_bundle: failed to fetch bundle " << bundle_id << ", result=" << result;
|
||||
return;
|
||||
}
|
||||
|
||||
// Import the updated bundle on the main thread
|
||||
CallAfter([this, bundle_id, bundle_presets, remote_metadata]() {
|
||||
if (!is_closing() && preset_bundle && app_config) {
|
||||
// Check the presets for any system vendors that need to be installed
|
||||
for (auto data : bundle_presets) {
|
||||
if (!check_preset_parent_available(data)) {
|
||||
add_pending_vendor_preset(data);
|
||||
}
|
||||
}
|
||||
load_pending_vendors();
|
||||
|
||||
preset_bundle->bundles.ReadLock();
|
||||
std::string initial_version = preset_bundle->bundles.m_bundles[bundle_id].version;
|
||||
preset_bundle->bundles.ReadUnlock();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "ORCA : CallAfter from update_single_bundle function actually updating subscribed presets";
|
||||
|
||||
preset_bundle->bundles.WriteLock();
|
||||
|
||||
preset_bundle->update_subscribed_presets(*app_config, bundle_presets, remote_metadata, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
preset_bundle->bundles.WriteUnlock();
|
||||
|
||||
std::string text = format(_L("%s updated from %s to %s"), remote_metadata.name, initial_version, remote_metadata.version);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
|
||||
auto* evt = new wxCommandEvent(EVT_UPDATE_BUNDLE_COMPLETE);
|
||||
// evt->SetString(wxString::FromUTF8(bundle_id));
|
||||
if (m_preset_bundle_dlg)
|
||||
wxQueueEvent(m_preset_bundle_dlg, evt);
|
||||
else
|
||||
delete evt;
|
||||
// wxQueueEvent(&wxGetApp(), evt); // GUI_App -> dialog
|
||||
|
||||
if (mainframe)
|
||||
mainframe->update_side_preset_ui();
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: successfully updated bundle " << bundle_id;
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GUI_App::sync_bundle(std::string bundle_id, std::string version)
|
||||
{
|
||||
// if(preset_bundle->bundles.pauseReads.load())
|
||||
// {
|
||||
// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "ORCA : Update thread sync_bundle function yielded to main thread. 1";
|
||||
// return; // if the main thread acquires the lock at the start of our operations, we will yield
|
||||
// }
|
||||
if (!m_agent || !m_agent->is_user_login()) return;
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
|
||||
if (!orca_agent) return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: checking bundle " << bundle_id << " for updates";
|
||||
|
||||
bool is_new = false;
|
||||
bool is_update = false;
|
||||
|
||||
preset_bundle->bundles.ReadLock(); // acquire a read lock to check for updates
|
||||
|
||||
// if bundle already downloaded, check for updates
|
||||
auto bundle_it = preset_bundle->bundles.m_bundles.find(bundle_id);
|
||||
if (bundle_it != preset_bundle->bundles.m_bundles.end()) {
|
||||
|
||||
// Check if remote version is newer using Semver comparison
|
||||
auto local_version = Semver::parse(bundle_it->second.version);
|
||||
auto remote_version = Semver::parse(version);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: comparing local version: " << local_version << " to remote version: " << remote_version;
|
||||
|
||||
if (!local_version || !remote_version) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "sync_bundle: failed to parse versions for bundle " << bundle_id
|
||||
<< " (local: " << local_version << ", remote: " << remote_version << ")";
|
||||
preset_bundle->bundles.ReadUnlock(); // unlock read when fail
|
||||
return;
|
||||
}
|
||||
if (remote_version <= local_version) {
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: bundle " << bundle_id << " is up-to-date (version " << local_version << ")";
|
||||
preset_bundle->bundles.ReadUnlock(); // unlock read when fail
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: updating bundle " << bundle_id
|
||||
<< " from version " << local_version
|
||||
<< " to version " << remote_version;
|
||||
is_update = true;
|
||||
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: pulling newly subscribed bundle " << bundle_id << " at version " << version;
|
||||
is_new = true;
|
||||
}
|
||||
|
||||
preset_bundle->bundles.ReadUnlock(); // yield the read lock after checking for updates
|
||||
|
||||
// if it is an update, we will lock and write
|
||||
std::string ver;
|
||||
if(is_update)
|
||||
{
|
||||
preset_bundle->bundles.WriteLock();
|
||||
preset_bundle->bundles.m_bundles[bundle_id].update_available = true;
|
||||
preset_bundle->bundles.m_bundles[bundle_id].is_subscribed = true;
|
||||
ver = preset_bundle->bundles.m_bundles[bundle_id].version;
|
||||
preset_bundle->bundles.WriteUnlock();
|
||||
}
|
||||
|
||||
if(app_config->get_bool("preset_bundle_auto_update") == true || is_new)
|
||||
{
|
||||
// Fetch the latest bundle data from cloud
|
||||
std::map<std::string, std::map<std::string, std::string>> bundle_presets;
|
||||
BundleMetadata remote_metadata;
|
||||
int result = orca_agent->get_shared_bundle(bundle_id, &bundle_presets, &remote_metadata);
|
||||
|
||||
if (result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "sync_bundle: failed to fetch bundle " << bundle_id << ", result=" << result;
|
||||
return;
|
||||
}
|
||||
|
||||
// Import the updated bundle on the main thread
|
||||
CallAfter([this, bundle_id, bundle_presets, remote_metadata,is_new,is_update,ver]() {
|
||||
if (!is_closing() && preset_bundle && app_config) {
|
||||
// Check the presets for any system vendors that need to be installed
|
||||
for (auto data : bundle_presets) {
|
||||
if (!check_preset_parent_available(data)) {
|
||||
add_pending_vendor_preset(data);
|
||||
}
|
||||
}
|
||||
load_pending_vendors();
|
||||
|
||||
// if(!preset_bundle->bundles.pauseReads.load()) // check again if we can actually update so as to not block the main thread
|
||||
// {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "ORCA : CallAfter from sync_bundle function actually updating subscribed presets";
|
||||
|
||||
preset_bundle->bundles.WriteLock();
|
||||
|
||||
preset_bundle->update_subscribed_presets(*app_config, bundle_presets, remote_metadata, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
preset_bundle->bundles.WriteUnlock();
|
||||
|
||||
if(is_new)
|
||||
{
|
||||
std::string text = format(_L("%s has been downloaded."), remote_metadata.name);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
}
|
||||
else if(is_update)
|
||||
{
|
||||
std::string text = format(_L("%s updated from %s to %s"), remote_metadata.name, ver, remote_metadata.version);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
}
|
||||
|
||||
auto* evt = new wxCommandEvent(EVT_UPDATE_BUNDLE_COMPLETE);
|
||||
// evt->SetString(wxString::FromUTF8(bundle_id));
|
||||
if (m_preset_bundle_dlg)
|
||||
wxQueueEvent(m_preset_bundle_dlg, evt);
|
||||
else
|
||||
delete evt;
|
||||
|
||||
if (mainframe)
|
||||
mainframe->update_side_preset_ui();
|
||||
BOOST_LOG_TRIVIAL(info) << "sync_bundle: successfully updated bundle " << bundle_id;
|
||||
// }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GUI_App::check_bundle_updates()
|
||||
{
|
||||
if (!m_agent || !m_agent->is_user_login()) return;
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
|
||||
if (!orca_agent) return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "check_bundle_updates: checking for bundle updates";
|
||||
|
||||
// Fetch all subscribed bundles from cloud
|
||||
std::vector<std::pair<std::string, std::string>> subscribed_bundles;
|
||||
std::vector<std::string> notfound;
|
||||
std::vector<std::string> unauthorized;
|
||||
int result = orca_agent->get_subscribed_bundles(&subscribed_bundles,notfound,unauthorized);
|
||||
|
||||
if (result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "check_bundle_updates: failed to fetch subscribed bundles, result=" << result;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!notfound.empty())
|
||||
{
|
||||
for(auto& n : notfound)
|
||||
{
|
||||
std::string text = format(_L("Bundle %s is no longer available."), n);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
}
|
||||
}
|
||||
if(!unauthorized.empty())
|
||||
{
|
||||
for(auto& i : unauthorized)
|
||||
{
|
||||
std::string text = format(_L("Bundle %s access is unauthorized."), i);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch presets for each bundle
|
||||
std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> subscribed_bundle_presets;
|
||||
std::map<std::string, BundleMetadata> subscribed_bundle_metadata;
|
||||
|
||||
for (const auto& bundle : subscribed_bundles) {
|
||||
std::map<std::string, std::map<std::string, std::string>> presets;
|
||||
BundleMetadata metadata;
|
||||
int preset_result = orca_agent->get_shared_bundle(bundle.first, &presets, &metadata);
|
||||
|
||||
if (preset_result == 0) {
|
||||
subscribed_bundle_presets[bundle.first] = presets;
|
||||
subscribed_bundle_metadata[bundle.first] = metadata;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "check_bundle_updates: Failed to get presets for bundle_id=" << bundle.first << ", result=" << preset_result;
|
||||
// Continue with other bundles even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through local bundles and check for updates
|
||||
if (!preset_bundle) return;
|
||||
|
||||
int bundles_checked = 0;
|
||||
int updates_available = 0;
|
||||
|
||||
for (auto& [bundle_id, local_metadata] : preset_bundle->bundles.m_bundles) {
|
||||
// Only check subscribed bundles (those with UUID-style IDs from Orca Cloud)
|
||||
// Skip external bundles (those with name+timestamp IDs)
|
||||
if (!local_metadata.is_subscribed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find corresponding remote metadata
|
||||
auto remote_it = subscribed_bundle_metadata.find(bundle_id);
|
||||
if (remote_it == subscribed_bundle_metadata.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_bundle_updates: bundle " << bundle_id << " not found in remote subscriptions";
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& remote_metadata = remote_it->second;
|
||||
|
||||
// Compare versions using Semver
|
||||
auto local_version = Semver::parse(local_metadata.version);
|
||||
auto remote_version = Semver::parse(remote_metadata.version);
|
||||
|
||||
if (!local_version || !remote_version) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "check_bundle_updates: failed to parse versions for bundle " << bundle_id
|
||||
<< " (local: " << local_metadata.version << ", remote: " << remote_metadata.version << ")";
|
||||
continue;
|
||||
}
|
||||
|
||||
bundles_checked++;
|
||||
|
||||
// Update the runtime-only flag if remote version is newer
|
||||
if (remote_version > local_version) {
|
||||
local_metadata.update_available = true;
|
||||
updates_available++;
|
||||
BOOST_LOG_TRIVIAL(info) << "check_bundle_updates: bundle " << bundle_id << " (" << local_metadata.name
|
||||
<< ") has update available: local=" << local_metadata.version
|
||||
<< ", remote=" << remote_metadata.version;
|
||||
} else {
|
||||
local_metadata.update_available = false;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "check_bundle_updates: checked " << bundles_checked
|
||||
<< " bundles, found " << updates_available << " updates available";
|
||||
}
|
||||
|
||||
bool GUI_App::unsubscribe_bundle(const std::string& id)
|
||||
{
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
|
||||
return orca_agent->unsubscribe_bundle(id);
|
||||
}
|
||||
|
||||
void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
{
|
||||
if (app_config->get_stealth_mode())
|
||||
@@ -5995,6 +6572,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
// has already start sync
|
||||
if (m_user_sync_token) return;
|
||||
|
||||
// Sync only when login
|
||||
ProgressFn progressFn;
|
||||
WasCancelledFn cancelFn;
|
||||
std::function<void(bool)> finishFn;
|
||||
@@ -6031,11 +6609,23 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
};
|
||||
}
|
||||
|
||||
// Do a one-time scan for files that may be pending deletion (e.g., was deleted while not connected to internet)
|
||||
// Scan for orphaned .info files on startup and add them to deletion queue
|
||||
scan_orphaned_info_files();
|
||||
process_delete_presets();
|
||||
|
||||
Bind(EVT_UPDATE_PRESET_BUNDLE,&GUI_App::update_single_bundle,this);
|
||||
|
||||
m_sync_update_thread = Slic3r::create_thread(
|
||||
[this, progressFn, cancelFn, finishFn, t = std::weak_ptr<int>(m_user_sync_token)] {
|
||||
// get setting list, update setting list
|
||||
std::string version = preset_bundle->get_vendor_profile_version(PresetBundle::ORCA_DEFAULT_BUNDLE).to_string();
|
||||
if(!m_agent) return;
|
||||
|
||||
// run check_and_fix_user_presets_syncinfo once before syncing to make sure all presets have correct sync_info
|
||||
// So that we can sync presets that are migrated from old version or users manually put preset files in preset folder
|
||||
preset_bundle->check_and_fix_user_presets_syncinfo(m_agent->get_user_id());
|
||||
|
||||
int ret = m_agent->get_setting_list2(version, [this](auto info) {
|
||||
auto type = info[BBL_JSON_KEY_TYPE];
|
||||
auto name = info[BBL_JSON_KEY_NAME];
|
||||
@@ -6055,13 +6645,20 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
}
|
||||
}, progressFn, cancelFn);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " get_setting_list2 ret = " << ret << " m_is_closing = " << m_is_closing;
|
||||
|
||||
finishFn(ret == 0);
|
||||
|
||||
int count = 0, sync_count = 0;
|
||||
// For orca specific syncing
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
|
||||
int tick_tock = -1, sync_count = 0; // tick_tock = -1 to immediately run sync the frist time this thread runs
|
||||
std::vector<Preset> presets_to_sync;
|
||||
std::vector<std::pair<std::string, std::string>> bundles_to_sync;
|
||||
std::unordered_set<std::string> bundles_synced;
|
||||
// Sync once immediately, then every 60 seconds.
|
||||
while (!t.expired()) {
|
||||
count++;
|
||||
if (count % 20 == 0) {
|
||||
++tick_tock;
|
||||
if (tick_tock % 120 == 0) {
|
||||
tick_tock = 0;
|
||||
if (m_agent) {
|
||||
if (!m_agent->is_user_login()) {
|
||||
continue;
|
||||
@@ -6104,28 +6701,120 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
});
|
||||
}
|
||||
|
||||
unsigned int http_code = 200;
|
||||
process_delete_presets();
|
||||
}
|
||||
|
||||
/* get list witch need to be deleted*/
|
||||
std::vector<string> delete_cache_presets = get_delete_cache_presets_lock();
|
||||
for (auto it = delete_cache_presets.begin(); it != delete_cache_presets.end();) {
|
||||
if ((*it).empty()) continue;
|
||||
std::string del_setting_id = *it;
|
||||
int result = m_agent->delete_setting(del_setting_id);
|
||||
if (result == 0) {
|
||||
preset_deleted_from_cloud(del_setting_id);
|
||||
it = delete_cache_presets.erase(it);
|
||||
m_create_preset_blocked = { false, false, false, false, false, false };
|
||||
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: delete success! setting id = " << del_setting_id;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "delete setting = " <<del_setting_id << " failed";
|
||||
it++;
|
||||
// sync subscribed bundles, if orca
|
||||
if (orca_agent)
|
||||
{
|
||||
bundles_to_sync.clear();
|
||||
bundles_synced.clear();
|
||||
std::vector<std::string> not_found;
|
||||
std::vector<std::string> unauthorized;
|
||||
|
||||
int result = orca_agent->get_subscribed_bundles(&bundles_to_sync, not_found, unauthorized);
|
||||
if (result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "start_sync_user_preset: failed to fetch subscribed bundles, result=" << result;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!not_found.empty())
|
||||
{
|
||||
for(auto& n : not_found)
|
||||
{
|
||||
std::string text = format(_L("Bundle %s is no longer available."), n);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
}
|
||||
}
|
||||
if(!unauthorized.empty())
|
||||
{
|
||||
for(auto& i : unauthorized)
|
||||
{
|
||||
std::string text = format(_L("Bundle %s access is unauthorized."), i);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
preset_bundle->bundles.ReadLock();
|
||||
if(preset_bundle->bundles.m_bundles.find(i) != preset_bundle->bundles.m_bundles.end())
|
||||
{
|
||||
preset_bundle->bundles.m_bundles[i].unauthorized = true;
|
||||
}
|
||||
preset_bundle->bundles.ReadUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the bundles, and update/create
|
||||
for (const auto& bundle_entry : bundles_to_sync) {
|
||||
|
||||
bundles_synced.insert(bundle_entry.first);
|
||||
// Sync each bundle individually
|
||||
// if(!preset_bundle->bundles.pauseReads.load()) // if pause is true we will skip updating this frame altogether
|
||||
// {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "ORCA : Update thread syncing bundles";
|
||||
sync_bundle(bundle_entry.first, bundle_entry.second);
|
||||
// }
|
||||
// Small delay between bundle syncs to avoid overwhelming the server
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||
|
||||
}
|
||||
|
||||
std::vector<BundleMetadata> to_delete;
|
||||
preset_bundle->bundles.ReadLock();
|
||||
for (const auto& [id, bundle] : preset_bundle->bundles.m_bundles) {
|
||||
if (bundle.bundle_type != BundleType::Subscribed)
|
||||
continue;
|
||||
if (bundles_synced.find(id) != bundles_synced.end())
|
||||
continue;
|
||||
if(bundle.unauthorized && bundle.is_subscribed)
|
||||
continue;
|
||||
|
||||
to_delete.push_back(bundle);
|
||||
}
|
||||
preset_bundle->bundles.ReadUnlock();
|
||||
|
||||
bool has_deletion = false;
|
||||
for (const auto& bundle : to_delete) {
|
||||
|
||||
// Delete the presets first (force=true: bundle presets have is_from_bundle=true)
|
||||
for (auto printer : bundle.printer_presets)
|
||||
preset_bundle->printers.delete_preset(printer, true);
|
||||
for (auto filament : bundle.filament_presets)
|
||||
preset_bundle->filaments.delete_preset(filament, true);
|
||||
for (auto print : bundle.print_presets)
|
||||
preset_bundle->prints.delete_preset(print, true);
|
||||
|
||||
// Delete the bundle folder and bundle
|
||||
fs::path bundle_folder = fs::path(bundle.path.c_str()).parent_path();
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove_all(bundle_folder, ec);
|
||||
|
||||
preset_bundle->bundles.WriteLock();
|
||||
preset_bundle->bundles.m_bundles.erase(bundle.id);
|
||||
preset_bundle->bundles.WriteUnlock();
|
||||
|
||||
std::string text = format(_L("%s has been removed."), bundle.name);
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification,NotificationManager::NotificationLevel::RegularNotificationLevel,text);
|
||||
has_deletion = true;
|
||||
}
|
||||
|
||||
// Update UI on main thread after deletion
|
||||
if (has_deletion)
|
||||
CallAfter([this]() {
|
||||
if (!is_closing() && preset_bundle && mainframe) {
|
||||
// update_compatible() ensures proper selection state after deletion
|
||||
preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
|
||||
preset_bundle->update_multi_material_filament_presets();
|
||||
mainframe->update_side_preset_ui();
|
||||
|
||||
auto* evt = new wxCommandEvent(EVT_UPDATE_BUNDLE_COMPLETE);
|
||||
// evt->SetString(wxString::FromUTF8(bundle_id));
|
||||
if (m_preset_bundle_dlg)
|
||||
wxQueueEvent(m_preset_bundle_dlg, evt);
|
||||
else
|
||||
delete evt;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -6145,19 +6834,27 @@ void GUI_App::stop_sync_user_preset()
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::start_http_server()
|
||||
void GUI_App::start_http_server(const std::string& provider)
|
||||
{
|
||||
m_http_server.set_request_handler([provider](const std::string& url) {
|
||||
return HttpServer::auth_handle_request(url, provider);
|
||||
});
|
||||
|
||||
if (!m_http_server.is_started())
|
||||
m_http_server.start();
|
||||
}
|
||||
|
||||
void GUI_App::start_http_server(int port)
|
||||
void GUI_App::start_http_server(int port, const std::string& provider)
|
||||
{
|
||||
if (port <= 0) {
|
||||
start_http_server();
|
||||
start_http_server(provider);
|
||||
return;
|
||||
}
|
||||
|
||||
m_http_server.set_request_handler([provider](const std::string& url) {
|
||||
return HttpServer::auth_handle_request(url, provider);
|
||||
});
|
||||
|
||||
if (m_http_server.is_started()) {
|
||||
if (m_http_server.get_port() == static_cast<boost::asio::ip::port_type>(port)) {
|
||||
return;
|
||||
@@ -6875,6 +7572,46 @@ void GUI_App::show_ip_address_enter_dialog_handler(wxCommandEvent& evt)
|
||||
// menu->AppendSubMenu(local_menu, _L("Configuration"));
|
||||
//}
|
||||
|
||||
void GUI_App::open_presetbundledialog(size_t open_on_tab, const std::string& highlight_option)
|
||||
{
|
||||
bool app_layout_changed = false;
|
||||
{
|
||||
|
||||
if(m_preset_bundle_dlg)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_preset_bundle_dlg = new PresetBundleDialog(mainframe, open_on_tab, highlight_option);
|
||||
m_preset_bundle_dlg->Bind(wxEVT_DESTROY, [this](wxWindowDestroyEvent&) {
|
||||
if (m_preset_bundle_dlg)
|
||||
m_preset_bundle_dlg = nullptr;
|
||||
});
|
||||
// PresetBundleDialog dlg(mainframe, open_on_tab, highlight_option);
|
||||
m_preset_bundle_dlg->ShowModal();
|
||||
if (m_preset_bundle_dlg) {
|
||||
m_preset_bundle_dlg->Destroy();
|
||||
m_preset_bundle_dlg = nullptr;
|
||||
}
|
||||
this->plater_->get_current_canvas3D()->force_set_focus();
|
||||
|
||||
}
|
||||
}
|
||||
void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::string& highlight_option)
|
||||
{
|
||||
bool app_layout_changed = false;
|
||||
{
|
||||
ExportPresetBundleDialog dlg(mainframe, open_on_tab, highlight_option);
|
||||
dlg.ShowModal();
|
||||
this->plater_->get_current_canvas3D()->force_set_focus();
|
||||
#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
|
||||
this->plater_->reload_print();
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
|
||||
{
|
||||
bool need_recreate_gui = false;
|
||||
@@ -7199,27 +7936,123 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch
|
||||
|
||||
static std::mutex mutex_delete_cache_presets;
|
||||
|
||||
std::vector<std::string> & GUI_App::get_delete_cache_presets()
|
||||
std::map<std::string, std::string> & GUI_App::get_delete_cache_presets()
|
||||
{
|
||||
return need_delete_presets;
|
||||
}
|
||||
|
||||
std::vector<std::string> GUI_App::get_delete_cache_presets_lock()
|
||||
std::map<std::string, std::string> GUI_App::get_delete_cache_presets_lock()
|
||||
{
|
||||
std::scoped_lock l(mutex_delete_cache_presets);
|
||||
return need_delete_presets;
|
||||
}
|
||||
|
||||
void GUI_App::delete_preset_from_cloud(std::string setting_id)
|
||||
void GUI_App::process_delete_presets()
|
||||
{
|
||||
std::map<string, string> delete_cache_presets = get_delete_cache_presets_lock();
|
||||
for (auto it = delete_cache_presets.begin(); it != delete_cache_presets.end();) {
|
||||
if (it->first.empty()) continue;
|
||||
std::string del_setting_id = it->first;
|
||||
int result = m_agent->delete_setting(del_setting_id);
|
||||
if (result == 0) {
|
||||
preset_deleted_from_cloud(del_setting_id);
|
||||
it = delete_cache_presets.erase(it);
|
||||
m_create_preset_blocked = { false, false, false, false, false, false };
|
||||
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: delete success! setting id = " << del_setting_id;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "delete setting = " <<del_setting_id << " failed";
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::delete_preset_from_cloud(std::string setting_id, std::string preset_file_path)
|
||||
{
|
||||
std::scoped_lock l(mutex_delete_cache_presets);
|
||||
need_delete_presets.push_back(setting_id);
|
||||
fs::path info_path = fs::path(preset_file_path);
|
||||
info_path.replace_extension("info");
|
||||
need_delete_presets.emplace(setting_id, info_path.string());
|
||||
}
|
||||
|
||||
void GUI_App::preset_deleted_from_cloud(std::string setting_id)
|
||||
{
|
||||
std::scoped_lock l(mutex_delete_cache_presets);
|
||||
need_delete_presets.erase(std::remove(need_delete_presets.begin(), need_delete_presets.end(), setting_id), need_delete_presets.end());
|
||||
|
||||
// Get the preset info path BEFORE erasing from the map
|
||||
std::string preset_file_path = need_delete_presets[setting_id];
|
||||
|
||||
// Delete the .info file after cloud deletion is confirmed
|
||||
if (!preset_file_path.empty() && fs::exists(fs::path(preset_file_path))) {
|
||||
boost::nowide::remove(preset_file_path.c_str());
|
||||
BOOST_LOG_TRIVIAL(info) << "Deleted .info file after cloud confirmation: " << preset_file_path;
|
||||
}
|
||||
|
||||
// Now erase from map
|
||||
need_delete_presets.erase(setting_id);
|
||||
}
|
||||
|
||||
// BBS: extract setting_id from .info file
|
||||
std::string GUI_App::extract_setting_id_from_info(const std::string& info_file_path)
|
||||
{
|
||||
boost::nowide::ifstream file(info_file_path);
|
||||
if (!file.is_open())
|
||||
return "";
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (line.find("setting_id") == 0) {
|
||||
size_t pos = line.find("=");
|
||||
if (pos != std::string::npos) {
|
||||
std::string setting_id = line.substr(pos + 1);
|
||||
// Trim whitespace
|
||||
setting_id.erase(0, setting_id.find_first_not_of(" \t"));
|
||||
setting_id.erase(setting_id.find_last_not_of(" \t\r\n") + 1);
|
||||
return setting_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Scan for orphaned .info files on startup
|
||||
void GUI_App::scan_orphaned_info_files()
|
||||
{
|
||||
// Get user preset directory
|
||||
std::string user_sub_folder = app_config->get("preset_folder");
|
||||
if (user_sub_folder.empty())
|
||||
user_sub_folder = DEFAULT_USER_FOLDER_NAME;
|
||||
const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + user_sub_folder;
|
||||
|
||||
// Scan for orphaned .info files in each preset type directory
|
||||
std::vector<std::string> preset_types = {PRESET_PRINT_NAME, PRESET_FILAMENT_NAME, PRESET_PRINTER_NAME};
|
||||
|
||||
for (const std::string& type : preset_types) {
|
||||
fs::path type_dir = fs::path(dir_user_presets) / type;
|
||||
if (!fs::exists(type_dir))
|
||||
continue;
|
||||
|
||||
// Iterate through all .info files
|
||||
for (auto& entry : boost::filesystem::directory_iterator(type_dir)) {
|
||||
if (entry.path().extension() != ".info")
|
||||
continue;
|
||||
|
||||
fs::path info_file = entry.path();
|
||||
fs::path preset_file = info_file;
|
||||
preset_file.replace_extension(".json");
|
||||
|
||||
// If .json doesn't exist, .info is orphaned
|
||||
if (!fs::exists(preset_file)) {
|
||||
// Extract setting_id from .info file
|
||||
std::string setting_id = extract_setting_id_from_info(info_file.string());
|
||||
if (!setting_id.empty()) {
|
||||
// Add to need_delete_presets
|
||||
delete_preset_from_cloud(setting_id, info_file.string());
|
||||
BOOST_LOG_TRIVIAL(info) << "Found orphaned .info file on startup: " << info_file.string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxString GUI_App::filter_string(wxString str)
|
||||
|
||||
+58
-26
@@ -6,11 +6,13 @@
|
||||
#include "ImGuiWrapper.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "PresetBundleDialog.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/GUI/UserNotification.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/Utils/BBLCloudServiceAgent.hpp"
|
||||
#include "slic3r/GUI/WebViewDialog.hpp"
|
||||
#include "slic3r/GUI/WebUserLoginDialog.hpp"
|
||||
#include "slic3r/GUI/BindDialog.hpp"
|
||||
@@ -293,13 +295,21 @@ private:
|
||||
Slic3r::UserManager* m_user_manager { nullptr };
|
||||
Slic3r::TaskManager* m_task_manager { nullptr };
|
||||
NetworkAgent* m_agent { nullptr };
|
||||
std::vector<std::string> need_delete_presets; // store setting ids of preset
|
||||
std::map<std::string, std::string> need_delete_presets; // store setting ids of preset
|
||||
std::vector<bool> m_create_preset_blocked { false, false, false, false, false, false }; // excceed limit
|
||||
bool m_networking_compatible { false };
|
||||
bool m_networking_need_update { false };
|
||||
bool m_networking_cancel_update { false };
|
||||
std::shared_ptr<UpgradeNetworkJob> m_upgrade_network_job;
|
||||
|
||||
// ORCA: for installing vendors on the main thread when presets to be synced requires it
|
||||
// vendor structure is:
|
||||
// [vendor_name]: { model: variants, model: variants, ... }
|
||||
// filaments structure is:
|
||||
// [filament_name]: true/false, ...
|
||||
std::map<std::string, std::map<std::string, std::set<std::string>>> need_add_vendors;
|
||||
std::map<std::string, std::string> need_add_filaments;
|
||||
|
||||
// login widget
|
||||
ZUserLogin* login_dlg { nullptr };
|
||||
|
||||
@@ -460,27 +470,29 @@ public:
|
||||
wxString transition_tridid(int trid_id) const;
|
||||
void ShowUserGuide();
|
||||
void ShowDownNetPluginDlg();
|
||||
void ShowUserLogin(bool show = true);
|
||||
void ShowUserLogin(bool show = true, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void ShowOnlyFilament();
|
||||
//BBS
|
||||
void request_login(bool show_user_info = false);
|
||||
bool check_login();
|
||||
void get_login_info();
|
||||
bool is_user_login();
|
||||
// Orca auth
|
||||
void request_login(bool show_user_info = false, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
const std::string& get_printer_cloud_provider() const;
|
||||
|
||||
void request_user_login(int online_login = 0);
|
||||
void request_user_handle(int online_login = 0);
|
||||
void request_user_logout();
|
||||
int request_user_unbind(std::string dev_id);
|
||||
void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void request_user_logout(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void post_logout_to_webview(const std::string& provider);
|
||||
int request_user_unbind(std::string dev_id, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string handle_web_request(std::string cmd);
|
||||
void handle_script_message(std::string msg);
|
||||
void handle_script_message(std::string msg, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void request_model_download(wxString url);
|
||||
void download_project(std::string project_id);
|
||||
void request_project_download(std::string project_id);
|
||||
void request_open_project(std::string project_id);
|
||||
void request_remove_project(std::string project_id);
|
||||
|
||||
void handle_http_error(unsigned int status, std::string body);
|
||||
void handle_http_error(unsigned int status, std::string body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void on_http_error(wxCommandEvent &evt);
|
||||
void on_update_machine_list(wxCommandEvent& evt);
|
||||
void on_user_login(wxCommandEvent &evt);
|
||||
@@ -507,19 +519,36 @@ public:
|
||||
void push_notification(const MachineObject* obj, wxString msg, wxString title = wxEmptyString, UserNotificationStyle style = UserNotificationStyle::UNS_NORMAL);
|
||||
void reload_settings();
|
||||
void remove_user_presets();
|
||||
|
||||
bool maybe_migrate_user_presets_on_login();
|
||||
|
||||
// ORCA: functions for loading unloaded vendors to allow for proper inheritance when syncing user presets/bundles
|
||||
bool check_preset_parent_available(const std::pair<std::string, std::map<std::string, std::string>>& preset_data);
|
||||
void add_pending_vendor_preset(const std::pair<std::string, std::map<std::string, std::string>>& preset_data);
|
||||
void load_pending_vendors();
|
||||
|
||||
void sync_preset(Preset* preset);
|
||||
void start_sync_user_preset(bool with_progress_dlg = false);
|
||||
void stop_sync_user_preset();
|
||||
void start_http_server();
|
||||
void start_http_server(int port);
|
||||
|
||||
// Bundle subscription sync
|
||||
void check_bundle_updates();
|
||||
void sync_bundle(std::string bundle_id, std::string version);
|
||||
bool unsubscribe_bundle(const std::string& id);
|
||||
void update_single_bundle(wxCommandEvent& evt);
|
||||
|
||||
PresetBundleDialog* m_preset_bundle_dlg{nullptr};
|
||||
|
||||
void start_http_server(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void start_http_server(int port, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void stop_http_server();
|
||||
void switch_staff_pick(bool on);
|
||||
|
||||
void on_show_check_privacy_dlg(int online_login = 0);
|
||||
void on_show_check_privacy_dlg(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void show_check_privacy_dlg(wxCommandEvent& evt);
|
||||
void on_check_privacy_update(wxCommandEvent &evt);
|
||||
bool check_privacy_update();
|
||||
void check_privacy_version(int online_login = 0);
|
||||
void check_privacy_version(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
void check_track_enable();
|
||||
|
||||
static bool catch_error(std::function<void()> cb, const std::string& err);
|
||||
@@ -560,10 +589,13 @@ public:
|
||||
bool checked_tab(Tab* tab);
|
||||
//BBS: add preset combox re-active logic
|
||||
void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true);
|
||||
std::vector<std::string> &get_delete_cache_presets();
|
||||
std::vector<std::string> get_delete_cache_presets_lock();
|
||||
void delete_preset_from_cloud(std::string setting_id);
|
||||
std::map<std::string, std::string> &get_delete_cache_presets();
|
||||
std::map<std::string, std::string> get_delete_cache_presets_lock();
|
||||
void process_delete_presets();
|
||||
void delete_preset_from_cloud(std::string setting_id, std::string preset_file_path);
|
||||
void preset_deleted_from_cloud(std::string setting_id);
|
||||
void scan_orphaned_info_files();
|
||||
static std::string extract_setting_id_from_info(const std::string& info_file_path);
|
||||
|
||||
wxString filter_string(wxString str);
|
||||
wxString current_language_code() const { return m_active_language_code.empty() && m_wxLocale ? m_wxLocale->GetCanonicalName() : m_active_language_code; }
|
||||
@@ -572,7 +604,8 @@ public:
|
||||
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }
|
||||
|
||||
void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
|
||||
|
||||
void open_presetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
|
||||
void open_exportpresetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
|
||||
virtual bool OnExceptionInMainLoop() override;
|
||||
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
|
||||
bool open_browser_with_warning_dialog(const wxString& url, int flags = 0);
|
||||
@@ -707,7 +740,7 @@ public:
|
||||
bool hot_reload_network_plugin();
|
||||
std::string get_latest_network_version() const;
|
||||
bool has_network_update_available() const;
|
||||
// Return the client version to report to Bambu servers. Pinned to
|
||||
// Orca: return the client version to report to Bambu servers. Pinned to
|
||||
// 01.10.01.50 when the legacy network plugin lacks get_my_token support
|
||||
// so the auth server stays on the ?access_token= redirect path.
|
||||
std::string get_bbl_client_version();
|
||||
@@ -722,10 +755,6 @@ private:
|
||||
void remove_old_networking_plugins();
|
||||
void drain_pending_events(int timeout_ms);
|
||||
bool wait_for_network_idle(int timeout_ms);
|
||||
//BBS set extra header for http request
|
||||
std::map<std::string, std::string> get_extra_header();
|
||||
void init_http_extra_header();
|
||||
void update_http_extra_header();
|
||||
bool check_older_app_config(Semver current_version, bool backup);
|
||||
void copy_older_config();
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
@@ -751,6 +780,9 @@ private:
|
||||
|
||||
DECLARE_APP(GUI_App)
|
||||
wxDECLARE_EVENT(EVT_CONNECT_LAN_MODE_PRINT, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_UPDATE_PRESET_BUNDLE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_UPDATE_BUNDLE_COMPLETE, wxCommandEvent);
|
||||
|
||||
|
||||
bool is_support_filament(int extruder_id, bool strict_check = true);
|
||||
bool is_soluble_filament(int extruder_id);
|
||||
|
||||
@@ -61,7 +61,7 @@ void session::read_body()
|
||||
int nbuffer = 1000;
|
||||
std::shared_ptr<std::vector<char>> bufptr = std::make_shared<std::vector<char>>(nbuffer);
|
||||
async_read(socket, boost::asio::buffer(*bufptr, nbuffer),
|
||||
[this, self](const boost::beast::error_code& e, std::size_t s) { server.stop(self); });
|
||||
[this, self, bufptr](const boost::beast::error_code& e, std::size_t s) { server.stop(self); });
|
||||
}
|
||||
|
||||
void session::read_next_line()
|
||||
@@ -146,18 +146,27 @@ void HttpServer::IOServer::stop_all()
|
||||
|
||||
HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port) {}
|
||||
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void HttpServer::start()
|
||||
{
|
||||
if (start_http_server)
|
||||
return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "start_http_service...";
|
||||
start_http_server = true;
|
||||
m_http_server_thread = create_thread([this] {
|
||||
server_ = std::make_unique<IOServer>(*this);
|
||||
IOServer* io_server = server_.get();
|
||||
start_http_server = true;
|
||||
m_http_server_thread = create_thread([io_server] {
|
||||
set_current_thread_name("http_server");
|
||||
server_ = std::make_unique<IOServer>(*this);
|
||||
server_->acceptor.listen();
|
||||
io_server->acceptor.listen();
|
||||
|
||||
server_->do_accept();
|
||||
io_server->do_accept();
|
||||
|
||||
server_->io_service.run();
|
||||
io_server->io_service.run();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,9 +174,14 @@ void HttpServer::stop()
|
||||
{
|
||||
start_http_server = false;
|
||||
if (server_) {
|
||||
server_->acceptor.close();
|
||||
server_->stop_all();
|
||||
server_->io_service.stop();
|
||||
IOServer* io_server = server_.get();
|
||||
boost::asio::post(io_server->io_service, [io_server] {
|
||||
boost::system::error_code ec;
|
||||
io_server->acceptor.cancel(ec);
|
||||
io_server->acceptor.close(ec);
|
||||
io_server->stop_all();
|
||||
io_server->io_service.stop();
|
||||
});
|
||||
}
|
||||
if (m_http_server_thread.joinable())
|
||||
m_http_server_thread.join();
|
||||
@@ -180,12 +194,20 @@ void HttpServer::set_request_handler(const std::function<std::shared_ptr<Respons
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const std::string& url)
|
||||
{
|
||||
return auth_handle_request(url, BBL_CLOUD_PROVIDER);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpServer::Response> HttpServer::auth_handle_request(const std::string& url, const std::string& provider)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response";
|
||||
|
||||
const std::string auth_code = url_get_param(url, "code");
|
||||
if (!auth_code.empty()) {
|
||||
std::string state = url_get_param(url, "state");
|
||||
std::string state = url_get_param(url, "orca_state");
|
||||
if (state.empty()) {
|
||||
state = url_get_param(url, "state"); // fallback
|
||||
}
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (!agent) {
|
||||
return std::make_shared<ResponseNotFound>();
|
||||
@@ -196,10 +218,10 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
payload["data"]["code"] = auth_code;
|
||||
payload["data"]["state"] = state;
|
||||
|
||||
agent->change_user(payload.dump());
|
||||
const bool login_ok = agent->is_user_login();
|
||||
agent->change_user(payload.dump(), provider);
|
||||
const bool login_ok = agent->is_user_login(provider);
|
||||
if (login_ok) {
|
||||
wxGetApp().request_user_login(1);
|
||||
wxGetApp().request_user_login(1, provider);
|
||||
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
|
||||
}
|
||||
|
||||
@@ -229,7 +251,7 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
|
||||
unsigned int http_code;
|
||||
std::string http_body;
|
||||
int result = agent->get_my_profile(access_token, &http_code, &http_body);
|
||||
int result = agent->get_my_profile(access_token, &http_code, &http_body, provider);
|
||||
if (result == 0) {
|
||||
std::string user_id;
|
||||
std::string user_name;
|
||||
@@ -257,9 +279,9 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
j["data"]["user"]["name"] = user_name;
|
||||
j["data"]["user"]["account"] = user_account;
|
||||
j["data"]["user"]["avatar"] = user_avatar;
|
||||
agent->change_user(j.dump());
|
||||
if (agent->is_user_login()) {
|
||||
wxGetApp().request_user_login(1);
|
||||
agent->change_user(j.dump(), provider);
|
||||
if (agent->is_user_login(provider)) {
|
||||
wxGetApp().request_user_login(1, provider);
|
||||
}
|
||||
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
|
||||
std::string location_str = (boost::format("%1%?result=success") % redirect_url).str();
|
||||
@@ -296,7 +318,7 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
|
||||
unsigned int token_http_code = 0;
|
||||
std::string token_body;
|
||||
int token_result = agent->get_my_token(ticket, &token_http_code, &token_body);
|
||||
int token_result = agent->get_my_token(ticket, &token_http_code, &token_body, provider);
|
||||
if (token_result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: get_my_token failed, http_code=" << token_http_code;
|
||||
return fail_redirect("get_my_token_error_" + std::to_string(token_result));
|
||||
@@ -326,7 +348,7 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
|
||||
unsigned int profile_http_code = 0;
|
||||
std::string profile_body;
|
||||
int profile_result = agent->get_my_profile(access_token, &profile_http_code, &profile_body);
|
||||
int profile_result = agent->get_my_profile(access_token, &profile_http_code, &profile_body, provider);
|
||||
if (profile_result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: get_my_profile failed, http_code=" << profile_http_code;
|
||||
return fail_redirect("get_user_profile_error_" + std::to_string(profile_result));
|
||||
@@ -359,9 +381,9 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
j["data"]["user"]["name"] = user_name;
|
||||
j["data"]["user"]["account"] = user_account;
|
||||
j["data"]["user"]["avatar"] = user_avatar;
|
||||
agent->change_user(j.dump());
|
||||
if (agent->is_user_login()) {
|
||||
wxGetApp().request_user_login(1);
|
||||
agent->change_user(j.dump(), provider);
|
||||
if (agent->is_user_login(provider)) {
|
||||
wxGetApp().request_user_login(1, provider);
|
||||
}
|
||||
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
|
||||
std::string location_str = (boost::format("%1%?result=success") % ticket_redirect_url).str();
|
||||
|
||||
@@ -110,6 +110,7 @@ public:
|
||||
};
|
||||
|
||||
HttpServer(boost::asio::ip::port_type port = LOCALHOST_PORT);
|
||||
~HttpServer();
|
||||
|
||||
boost::thread m_http_server_thread;
|
||||
bool start_http_server = false;
|
||||
@@ -122,6 +123,7 @@ public:
|
||||
void set_request_handler(const std::function<std::shared_ptr<Response>(const std::string&)>& m_request_handler);
|
||||
|
||||
static std::shared_ptr<Response> bbl_auth_handle_request(const std::string& url);
|
||||
static std::shared_ptr<Response> auth_handle_request(const std::string& url, const std::string& provider);
|
||||
|
||||
private:
|
||||
class IOServer
|
||||
|
||||
@@ -125,7 +125,7 @@ void BindJob::process(Ctl &ctl)
|
||||
post_fail_event(result_code, result_info);
|
||||
return;
|
||||
}
|
||||
dev->update_user_machine_list_info();
|
||||
dev->update_user_machine_list_info(wxGetApp().get_printer_cloud_provider());
|
||||
|
||||
wxCommandEvent event(EVT_BIND_MACHINE_SUCCESS);
|
||||
event.SetEventObject(m_event_handle);
|
||||
|
||||
@@ -3243,6 +3243,15 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().open_preferences();
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
|
||||
append_menu_item(
|
||||
m_topbar->GetTopMenu(), wxID_ANY, _L("Preset Bundle") + "\t", "",
|
||||
[this](wxCommandEvent &) {
|
||||
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
|
||||
wxGetApp().open_presetbundledialog();
|
||||
plater()->get_current_canvas3D()->force_set_focus();
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
//m_topbar->AddDropDownMenuItem(preference_item);
|
||||
//m_topbar->AddDropDownMenuItem(printer_item);
|
||||
//m_topbar->AddDropDownMenuItem(language_item);
|
||||
@@ -3343,6 +3352,15 @@ void MainFrame::init_menubar_as_editor()
|
||||
{return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
#else
|
||||
// On Mac, the Apple menu ignores non-standard custom items, so add Preset Bundle to the File menu
|
||||
fileMenu->AppendSeparator();
|
||||
append_menu_item(
|
||||
fileMenu, wxID_ANY, _L("Preset Bundle"), "",
|
||||
[this](wxCommandEvent &) {
|
||||
wxGetApp().open_presetbundledialog();
|
||||
plater()->get_current_canvas3D()->force_set_focus();
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
m_menubar->Append(fileMenu, wxString::Format("&%s", _L("File")));
|
||||
if (editMenu)
|
||||
m_menubar->Append(editMenu, wxString::Format("&%s", _L("Edit")));
|
||||
@@ -3645,7 +3663,7 @@ void MainFrame::load_config_file()
|
||||
// return;
|
||||
wxFileDialog dlg(this, _L("Select profile to load:"),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
"config.json", "Config files (*.json;*.zip;*.orca_printer;*.orca_filament)|*.json;*.zip;*.orca_printer;*.orca_filament", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
"config.json", "Config files (*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament)|*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
wxArrayString files;
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
@@ -3662,11 +3680,16 @@ void MainFrame::load_config_file()
|
||||
int ids[]{wxID_NO, wxID_YES, wxID_NOTOALL, wxID_YESTOALL};
|
||||
return std::find(ids, ids + 4, res) - ids;
|
||||
},
|
||||
ForwardCompatibilitySubstitutionRule::Enable);
|
||||
ForwardCompatibilitySubstitutionRule::Enable,
|
||||
*wxGetApp().app_config);
|
||||
if (!cfiles.empty()) {
|
||||
wxGetApp().app_config->update_config_dir(get_dir_name(cfiles.back()));
|
||||
wxGetApp().load_current_presets();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " presets has been import,and size is" << cfiles.size();
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (agent) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " user is: " << agent->get_user_id();
|
||||
}
|
||||
}
|
||||
wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Always);
|
||||
update_side_preset_ui();
|
||||
@@ -4213,7 +4236,7 @@ void MainFrame::update_side_preset_ui()
|
||||
void MainFrame::on_select_default_preset(SimpleEvent& evt)
|
||||
{
|
||||
MessageDialog dialog(this,
|
||||
_L("Do you want to synchronize your personal data from Bambu Cloud?\n"
|
||||
_L("Do you want to synchronize your personal data from Orca Cloud?\n"
|
||||
"It contains the following information:\n"
|
||||
"1. The Process presets\n"
|
||||
"2. The Filament presets\n"
|
||||
|
||||
@@ -362,7 +362,7 @@ void MonitorPanel::update_all()
|
||||
// only disconnected server in cloud mode
|
||||
if (obj->connection_type() != "lan") {
|
||||
if (m_agent) {
|
||||
server_status = m_agent->is_server_connected() ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
|
||||
server_status = m_agent->is_server_connected(wxGetApp().get_printer_cloud_provider()) ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
|
||||
}
|
||||
}
|
||||
show_status((int) MONITOR_DISCONNECTED + server_status);
|
||||
|
||||
@@ -2231,6 +2231,138 @@ void NotificationManager::push_import_finished_notification(const std::string& p
|
||||
set_slicing_progress_hidden();
|
||||
}
|
||||
|
||||
// SharedProfilesNotification implementation
|
||||
|
||||
void NotificationManager::SharedProfilesNotification::init()
|
||||
{
|
||||
PopNotification::init();
|
||||
// Add one extra line for the hyperlink row ("Browse shared profiles" + "Don't show again")
|
||||
m_lines_count++;
|
||||
}
|
||||
|
||||
void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
float x_offset = m_left_indentation;
|
||||
float shift_y = m_line_height;
|
||||
float starting_y = m_line_height / 2;
|
||||
|
||||
// Render main text line(s)
|
||||
int last_end = 0;
|
||||
std::string line;
|
||||
for (size_t i = 0; i < m_endlines.size(); i++) {
|
||||
if (m_text1.size() >= m_endlines[i]) {
|
||||
line = m_text1.substr(last_end, m_endlines[i] - last_end);
|
||||
last_end = m_endlines[i];
|
||||
if (m_text1.size() > m_endlines[i])
|
||||
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(starting_y + i * shift_y);
|
||||
imgui.text(line.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Render "Browse shared profiles" hyperlink on the next line
|
||||
float hyper_y = starting_y + m_endlines.size() * shift_y;
|
||||
render_hypertext(imgui, x_offset, hyper_y, m_hypertext);
|
||||
|
||||
// Render "Don't show again" hyperlink after the browse link
|
||||
{
|
||||
float dont_show_x = x_offset + ImGui::CalcTextSize((m_hypertext + " ").c_str()).x;
|
||||
std::string dont_show_text = _u8L("Don't show again");
|
||||
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
|
||||
|
||||
// Invisible button
|
||||
ImGui::SetCursorPosX(dont_show_x - 4);
|
||||
ImGui::SetCursorPosY(hyper_y - 5);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
if (imgui.button("##dont_show_btn", part_size.x + 6, part_size.y + 10)) {
|
||||
wxGetApp().app_config->set_bool("show_shared_profiles_notification", false);
|
||||
wxGetApp().app_config->save();
|
||||
close();
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Hover color
|
||||
ImVec4 color = m_HyperTextColor;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
color.y += 0.1f;
|
||||
|
||||
// Text
|
||||
push_style_color(ImGuiCol_Text, color, m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::SetCursorPosX(dont_show_x);
|
||||
ImGui::SetCursorPosY(hyper_y);
|
||||
imgui.text(dont_show_text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Underline
|
||||
ImVec2 lineEnd = ImGui::GetItemRectMax();
|
||||
lineEnd.y -= 2;
|
||||
ImVec2 lineStart = lineEnd;
|
||||
lineStart.x = ImGui::GetItemRectMin().x;
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
|
||||
IM_COL32((int)(color.x * 255), (int)(color.y * 255), (int)(color.z * 255),
|
||||
(int)(color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
|
||||
}
|
||||
}
|
||||
|
||||
bool NotificationManager::SharedProfilesNotification::on_text_click()
|
||||
{
|
||||
wxLaunchDefaultBrowser(m_explore_url);
|
||||
return false;
|
||||
}
|
||||
|
||||
void NotificationManager::SharedProfilesNotification::render_hypertext(ImGuiWrapper& imgui,
|
||||
const float text_x, const float text_y, const std::string text, bool more)
|
||||
{
|
||||
// Invisible button
|
||||
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
|
||||
ImGui::SetCursorPosX(text_x - 4);
|
||||
ImGui::SetCursorPosY(text_y - 5);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
if (imgui.button("##browse_btn", part_size.x + 6, part_size.y + 10)) {
|
||||
if (on_text_click()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Hover color
|
||||
ImVec4 HyperColor = m_HyperTextColor;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
HyperColor.y += 0.1f;
|
||||
|
||||
// Text
|
||||
push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::SetCursorPosX(text_x);
|
||||
ImGui::SetCursorPosY(text_y);
|
||||
imgui.text(text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Underline
|
||||
ImVec2 lineEnd = ImGui::GetItemRectMax();
|
||||
lineEnd.y -= 2;
|
||||
ImVec2 lineStart = lineEnd;
|
||||
lineStart.x = ImGui::GetItemRectMin().x;
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
|
||||
IM_COL32((int)(HyperColor.x * 255), (int)(HyperColor.y * 255), (int)(HyperColor.z * 255),
|
||||
(int)(HyperColor.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
|
||||
}
|
||||
|
||||
void NotificationManager::push_shared_profiles_notification(const std::string& explore_url)
|
||||
{
|
||||
close_notification_of_type(NotificationType::OrcaSharedProfilesAvailable);
|
||||
NotificationData data{ NotificationType::OrcaSharedProfilesAvailable, NotificationLevel::RegularNotificationLevel, 0,
|
||||
_u8L("Shared profiles may be available for this printer."),
|
||||
_u8L("Browse shared profiles") };
|
||||
push_notification_data(std::make_unique<NotificationManager::SharedProfilesNotification>(data, m_id_provider, m_evt_handler, explore_url), 0);
|
||||
}
|
||||
|
||||
void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback)
|
||||
{
|
||||
// If already exists
|
||||
|
||||
@@ -161,6 +161,7 @@ enum class NotificationType
|
||||
BBLBedFilamentIncompatible,
|
||||
BBLMixUsePLAAndPETG,
|
||||
BBLNozzleFilamentIncompatible,
|
||||
OrcaSharedProfilesAvailable,
|
||||
NotificationTypeCount
|
||||
|
||||
};
|
||||
@@ -271,6 +272,9 @@ public:
|
||||
void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
|
||||
void push_import_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
|
||||
|
||||
// Shared profiles available for selected printer
|
||||
void push_shared_profiles_notification(const std::string& explore_url);
|
||||
|
||||
// Download URL progress notif
|
||||
void push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback);
|
||||
void set_download_URL_progress(size_t id, float percentage);
|
||||
@@ -858,6 +862,30 @@ private:
|
||||
};
|
||||
|
||||
// in SlicingProgressNotification.hpp
|
||||
|
||||
class SharedProfilesNotification : public PopNotification
|
||||
{
|
||||
public:
|
||||
SharedProfilesNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler,
|
||||
const std::string& explore_url)
|
||||
: PopNotification(n, id_provider, evt_handler)
|
||||
, m_explore_url(explore_url)
|
||||
{
|
||||
m_multiline = true;
|
||||
}
|
||||
protected:
|
||||
void init() override;
|
||||
void render_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y) override;
|
||||
bool on_text_click() override;
|
||||
void render_hypertext(ImGuiWrapper& imgui,
|
||||
const float text_x, const float text_y,
|
||||
const std::string text, bool more = false) override;
|
||||
|
||||
std::string m_explore_url;
|
||||
bool m_dont_show_clicked{ false };
|
||||
};
|
||||
class SlicingProgressNotification;
|
||||
|
||||
// in HintNotification.hpp
|
||||
|
||||
@@ -161,6 +161,8 @@
|
||||
#include "DailyTips.hpp"
|
||||
#include "CreatePresetsDialog.hpp"
|
||||
#include "FileArchiveDialog.hpp"
|
||||
#include "../Utils/Http.hpp"
|
||||
#include "../Utils/OrcaCloudServiceAgent.hpp"
|
||||
#include "StepMeshDialog.hpp"
|
||||
#include "FilamentMapDialog.hpp"
|
||||
#include "CloneDialog.hpp"
|
||||
@@ -9423,12 +9425,17 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
//! instead of
|
||||
//! combo->GetStringSelection().ToUTF8().data());
|
||||
|
||||
wxString wx_name = combo->GetString(selection);
|
||||
// if (preset_type == Preset::TYPE_PRINTER) {
|
||||
// wx_name = combo->get_preset_item_name(selection); }
|
||||
|
||||
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
|
||||
Preset::remove_suffix_modified(wx_name.ToUTF8().data()));
|
||||
// Prefer the internal preset name stored per combo item to avoid alias collisions
|
||||
// (e.g. user preset and bundle preset sharing the same display alias).
|
||||
wxString wx_stored_name = combo->GetItemAlias(selection);
|
||||
std::string preset_name;
|
||||
if (!wx_stored_name.empty()) {
|
||||
preset_name = Preset::remove_suffix_modified(wx_stored_name.ToUTF8().data());
|
||||
} else {
|
||||
wxString wx_name = combo->GetString(selection);
|
||||
preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
|
||||
Preset::remove_suffix_modified(wx_name.ToUTF8().data()));
|
||||
}
|
||||
|
||||
if (preset_type == Preset::TYPE_FILAMENT) {
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
|
||||
@@ -9540,6 +9547,26 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
for (size_t idx = 0; idx < filament_size; ++idx)
|
||||
wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(idx);
|
||||
#endif
|
||||
|
||||
// Show shared profiles notification for the newly selected printer
|
||||
if (wxGetApp().app_config->get_bool("show_shared_profiles_notification"))
|
||||
{
|
||||
std::string printer_name = wxGetApp().preset_bundle->printers.get_selected_preset_base().name;
|
||||
std::string encoded_name = Http::url_encode(printer_name);
|
||||
|
||||
std::string cloud_url;
|
||||
if (auto agent = wxGetApp().getAgent()) {
|
||||
if (auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(agent->get_cloud_agent())) {
|
||||
cloud_url = orca_agent->get_cloud_base_url();
|
||||
}
|
||||
}
|
||||
if (cloud_url.empty())
|
||||
cloud_url = "https://cloud.orcaslicer.com";
|
||||
|
||||
std::string explore_url = cloud_url + "/app/bundles/explore?printers=" + encoded_name;
|
||||
|
||||
wxGetApp().plater()->get_notification_manager()->push_shared_profiles_notification(explore_url);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
|
||||
@@ -1356,6 +1356,9 @@ void PreferencesDialog::create_items()
|
||||
auto item_show_splash_scr = create_item_checkbox(_L("Show splash screen"), _L("Show the splash screen during startup."), "show_splash_screen");
|
||||
g_sizer->Add(item_show_splash_scr);
|
||||
|
||||
auto item_shared_profiles = create_item_checkbox(_L("Show shared profiles notification"), _L("Show a notification with a link to browse shared profiles when the selected printer is changed."), "show_shared_profiles_notification");
|
||||
g_sizer->Add(item_shared_profiles);
|
||||
|
||||
#ifdef __linux__
|
||||
auto item_window_button_pos = create_item_checkbox(_L("Use window buttons on left side"), "", "window_buttons_on_left", _L("(Requires restart)"));
|
||||
g_sizer->Add(item_window_button_pos);
|
||||
@@ -1534,6 +1537,45 @@ void PreferencesDialog::create_items()
|
||||
});
|
||||
g_sizer->Add(item_network_test);
|
||||
|
||||
//// ONLINE > Cloud Providers
|
||||
g_sizer->Add(create_item_title(_L("Cloud Providers")), 1, wxEXPAND);
|
||||
|
||||
{
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
|
||||
|
||||
auto text = new wxStaticText(m_parent, wxID_ANY, _L("Enable Bambu Cloud"),
|
||||
wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
|
||||
text->SetForegroundColour(DESIGN_GRAY900_COLOR);
|
||||
text->SetFont(::Label::Body_14);
|
||||
text->SetToolTip(_L("Allow logging into Bambu Cloud alongside Orca Cloud. When enabled, a Bambu login section appears on the homepage."));
|
||||
text->Wrap(DESIGN_TITLE_SIZE.x);
|
||||
|
||||
auto cb = new ::CheckBox(m_parent);
|
||||
cb->SetValue(app_config->has_cloud_provider(BBL_CLOUD_PROVIDER));
|
||||
cb->SetToolTip(text->GetToolTipText());
|
||||
|
||||
cb->Bind(wxEVT_TOGGLEBUTTON, [this, cb](wxCommandEvent &e) {
|
||||
e.Skip(); // let CheckBox::update() refresh the bitmap
|
||||
if (cb->GetValue()) {
|
||||
app_config->add_cloud_provider(BBL_CLOUD_PROVIDER);
|
||||
} else {
|
||||
app_config->remove_cloud_provider(BBL_CLOUD_PROVIDER);
|
||||
}
|
||||
app_config->save();
|
||||
|
||||
// Update homepage visibility immediately
|
||||
auto *mainframe = wxGetApp().mainframe;
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
});
|
||||
|
||||
sizer->Add(text, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
|
||||
sizer->Add(cb, 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5));
|
||||
|
||||
g_sizer->Add(sizer);
|
||||
}
|
||||
|
||||
//// ONLINE > Update & sync
|
||||
g_sizer->Add(create_item_title(_L("Update & sync")), 1, wxEXPAND);
|
||||
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
#include "PresetBundleDialog.hpp"
|
||||
#include "ExportPresetBundleDialog.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <libslic3r/Thread.hpp>
|
||||
#include <wx/app.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
#include <wx/string.h>
|
||||
#include "MainFrame.hpp"
|
||||
#include <slic3r/GUI/Widgets/WebView.hpp>
|
||||
#include <miniz.h>
|
||||
#include <OrcaCloudServiceAgent.hpp>
|
||||
#include <wx/event.h>
|
||||
#include <wx/utils.h>
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
PresetBundleDialog::PresetBundleDialog(
|
||||
wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
|
||||
: DPIDialog(parent, id, _L("PresetBundle"), pos, size, style)
|
||||
{
|
||||
wxGetApp().preset_bundle->bundles.PauseRead(); // for the entirety of the preset bundle dialog, we want the update thread to yield.
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetMinSize(DESIGN_WINDOW_SIZE);
|
||||
create();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
m_watcher = new wxFileSystemWatcher();
|
||||
m_watcher->SetOwner(this);
|
||||
|
||||
Bind(wxEVT_FSWATCHER, &PresetBundleDialog::OnFSWatch, this);
|
||||
Bind(EVT_UPDATE_BUNDLE_COMPLETE, &PresetBundleDialog::OnBundleUpdate, this);
|
||||
|
||||
m_watcher->Add(wxFileName(wxGetApp().preset_bundle->dir_user_presets_local.c_str())); // _local
|
||||
m_watcher->Add(wxFileName(wxGetApp().preset_bundle->dir_user_presets_subscribed.c_str())); // _subscribed
|
||||
|
||||
RefreshBundleMap();
|
||||
|
||||
StartDialogWorker(); // start worker thread;
|
||||
}
|
||||
|
||||
PresetBundleDialog::~PresetBundleDialog()
|
||||
{
|
||||
StopDialogWorker();
|
||||
wxGetApp().preset_bundle->bundles.UnpauseRead(); // yield for update thread
|
||||
if (m_watcher) {
|
||||
m_watcher->RemoveAll();
|
||||
delete m_watcher;
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundleDialog::OnFSWatch(wxFileSystemWatcherEvent& e)
|
||||
{
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
|
||||
wxGetApp().mainframe->update_side_preset_ui();
|
||||
|
||||
// ListBundles();
|
||||
m_update_bundles.store(true);
|
||||
e.Skip();
|
||||
}
|
||||
|
||||
void PresetBundleDialog::StartDialogWorker()
|
||||
{
|
||||
if (m_dialog_worker_token) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_dialog_worker_token = std::make_shared<int>(0);
|
||||
|
||||
m_dialog_worker_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_dialog_worker_token)] {
|
||||
while (!token.expired()) {
|
||||
// after comparing version with the cloud, if there is an update, we will update the rows to reflect
|
||||
|
||||
if (m_check_update_pending.exchange(false, std::memory_order_relaxed)) {
|
||||
if (CheckUpdateCloud()) {
|
||||
ListBundles();
|
||||
}
|
||||
}
|
||||
if (m_update_bundles.exchange(false, std::memory_order_relaxed)) {
|
||||
RefreshBundleMap();
|
||||
ListBundles();
|
||||
}
|
||||
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// true if b>a
|
||||
bool PresetBundleDialog::CompareVer(const std::string& a, const std::string& b)
|
||||
{
|
||||
// Compare versions using Semver
|
||||
auto local_version = Semver::parse(a);
|
||||
auto remote_version = Semver::parse(b);
|
||||
|
||||
if (!local_version || !remote_version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (local_version < remote_version) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PresetBundleDialog::CheckUpdateCloud()
|
||||
{
|
||||
bool has_update = false;
|
||||
if (!wxGetApp().getAgent() || !wxGetApp().getAgent()->is_user_login())
|
||||
return false;
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(wxGetApp().getAgent()->get_cloud_agent());
|
||||
if (!orca_agent)
|
||||
return false;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Preset Bundle Dialog: checking for bundle updates";
|
||||
|
||||
// Fetch all subscribed bundles from cloud
|
||||
std::vector<std::pair<std::string, std::string>> subscribed_bundles;
|
||||
std::vector<std::string> notfound;
|
||||
std::vector<std::string> unauthorized;
|
||||
int result = orca_agent->get_subscribed_bundles(&subscribed_bundles, notfound, unauthorized);
|
||||
|
||||
if (result != 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset Bundle Dialog: failed to fetch subscribed bundles, result=" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
// if unauthorized or not found it should be a warning icon to the ui shit
|
||||
|
||||
// check bundle copy with the subscribed bundles
|
||||
for (auto& b : subscribed_bundles) {
|
||||
if (bundle_copy.find(b.first) != bundle_copy.end()) {
|
||||
if (CompareVer(bundle_copy[b.first].version, b.second)) {
|
||||
bundle_copy[b.first].update_available = true;
|
||||
bundle_copy[b.first].unauthorized = false;
|
||||
has_update = true;
|
||||
|
||||
} else {
|
||||
// we count it as an update to the UI if we need to update the unauthorized state
|
||||
if (bundle_copy[b.first].unauthorized) {
|
||||
bundle_copy[b.first].unauthorized = false;
|
||||
has_update = true;
|
||||
}
|
||||
bundle_copy[b.first].update_available = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& a : unauthorized) {
|
||||
if (bundle_copy.find(a) != bundle_copy.end()) {
|
||||
bundle_copy[a].unauthorized = true;
|
||||
has_update = true;
|
||||
}
|
||||
}
|
||||
return has_update;
|
||||
}
|
||||
|
||||
void PresetBundleDialog::StopDialogWorker()
|
||||
{
|
||||
if (!m_dialog_worker_token) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_dialog_worker_token.reset();
|
||||
|
||||
if (m_dialog_worker_thread.joinable()) {
|
||||
m_dialog_worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundleDialog::OnBundleUpdate(wxCommandEvent& evt)
|
||||
{
|
||||
// const std::string bundle_id = evt.GetString().ToStdString();
|
||||
m_update_bundles.store(true);
|
||||
}
|
||||
|
||||
void PresetBundleDialog::RefreshBundleMap()
|
||||
{
|
||||
wxGetApp().preset_bundle->bundles.ReadLock();
|
||||
bundle_copy = wxGetApp().preset_bundle->bundles.m_bundles;
|
||||
wxGetApp().preset_bundle->bundles.ReadUnlock();
|
||||
}
|
||||
|
||||
void PresetBundleDialog::load_url(wxString& url)
|
||||
{
|
||||
if (!m_browser)
|
||||
return;
|
||||
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " enter, url=" << url.ToStdString();
|
||||
WebView::LoadUrl(m_browser, url);
|
||||
m_browser->SetFocus();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " exit";
|
||||
}
|
||||
|
||||
void PresetBundleDialog::create()
|
||||
{
|
||||
app_config = get_app_config();
|
||||
|
||||
wxString TargetUrl = from_u8(
|
||||
(boost::filesystem::path(resources_dir()) / "web/dialog/PresetBundleDialog/index.html").make_preferred().string());
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", strlang=%1%") % into_u8(strlang);
|
||||
if (strlang != "")
|
||||
TargetUrl = wxString::Format("%s?lang=%s", std::string(TargetUrl.mb_str()), strlang);
|
||||
|
||||
TargetUrl = "file://" + TargetUrl;
|
||||
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
SetTitle(_L("Preset Bundle"));
|
||||
|
||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||
if (m_browser == nullptr) {
|
||||
wxLogError("Could not init m_browser");
|
||||
return;
|
||||
}
|
||||
|
||||
SetSizer(topsizer);
|
||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||
|
||||
// Set a more sensible size for web browsing
|
||||
wxSize pSize = FromDIP(wxSize(820, 660));
|
||||
SetSize(pSize);
|
||||
|
||||
int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
|
||||
int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
|
||||
int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
|
||||
wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY);
|
||||
Move(tmpPT);
|
||||
|
||||
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PresetBundleDialog::OnScriptMessage, this, m_browser->GetId());
|
||||
|
||||
load_url(TargetUrl);
|
||||
}
|
||||
|
||||
bool PresetBundleDialog::DeleteBundleById(const wxString& id)
|
||||
{
|
||||
auto* b = wxGetApp().preset_bundle;
|
||||
if (id.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string bundle_id = id.ToStdString();
|
||||
|
||||
wxGetApp().preset_bundle->bundles.ReadLock();
|
||||
auto it = b->bundles.m_bundles.find(bundle_id);
|
||||
if (it == b->bundles.m_bundles.end()) {
|
||||
wxGetApp().preset_bundle->bundles.ReadUnlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string metadata_path = it->second.path;
|
||||
const boost::filesystem::path bundle_dir = boost::filesystem::path(metadata_path).parent_path();
|
||||
|
||||
const BundleType bundle_type = it->second.bundle_type;
|
||||
wxGetApp().preset_bundle->bundles.ReadUnlock();
|
||||
|
||||
if (bundle_type == BundleType::Subscribed) {
|
||||
// do unsubscribe before deleting locally
|
||||
}
|
||||
|
||||
wxGetApp().preset_bundle->bundles.WriteLock();
|
||||
b->bundles.m_bundles.erase(it);
|
||||
wxGetApp().preset_bundle->bundles.WriteUnlock();
|
||||
|
||||
auto remove_from_collection = [&](PresetCollection& c) {
|
||||
std::vector<std::string> to_delete;
|
||||
for (const auto& p : c.get_presets()) {
|
||||
if (p.bundle_id == bundle_id)
|
||||
to_delete.push_back(p.name);
|
||||
}
|
||||
for (const auto& name : to_delete)
|
||||
c.delete_preset(name);
|
||||
};
|
||||
|
||||
remove_from_collection(b->prints);
|
||||
remove_from_collection(b->filaments);
|
||||
remove_from_collection(b->printers);
|
||||
|
||||
boost::system::error_code ec;
|
||||
if (!bundle_dir.empty() && boost::filesystem::exists(bundle_dir))
|
||||
boost::filesystem::remove_all(bundle_dir, ec);
|
||||
|
||||
wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Always);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PresetBundleDialog::UnsubscribeBundleById(const std::string& id) { return wxGetApp().unsubscribe_bundle(id); }
|
||||
|
||||
void PresetBundleDialog::on_dpi_changed(const wxRect& suggested_rect) { this->Refresh(); }
|
||||
|
||||
void PresetBundleDialog::RunScript(const wxString& s)
|
||||
{
|
||||
if (!m_browser)
|
||||
return;
|
||||
|
||||
WebView::RunScript(m_browser, s);
|
||||
}
|
||||
|
||||
void PresetBundleDialog::OnScriptMessage(wxWebViewEvent& e)
|
||||
{
|
||||
try {
|
||||
wxString strInput = e.GetString();
|
||||
BOOST_LOG_TRIVIAL(trace) << "PresetBundleDialog::OnScriptMessage;OnRecv:" << strInput.c_str();
|
||||
json j = json::parse(strInput.utf8_string());
|
||||
|
||||
wxString strCmd = j["command"];
|
||||
BOOST_LOG_TRIVIAL(trace) << "PresetBundleDialog::OnScriptMessage;Command:" << strCmd;
|
||||
|
||||
if (strCmd == "request_bundles") {
|
||||
ListBundles();
|
||||
} else if (strCmd == "refresh_bundles") {
|
||||
// use the thread to check for updates.
|
||||
m_check_update_pending.store(true, std::memory_order_relaxed);
|
||||
} else if (strCmd == "update_bundle") {
|
||||
std::string id = j["bundle_id"];
|
||||
|
||||
auto* evt = new wxCommandEvent(EVT_UPDATE_PRESET_BUNDLE);
|
||||
evt->SetString(wxString::FromUTF8(id));
|
||||
wxQueueEvent(&wxGetApp(), evt); // dialog -> GUI_App
|
||||
} else if (strCmd == "set_auto_update") {
|
||||
bool enabled = j.value("enabled", false);
|
||||
|
||||
// Example persistence location. Adjust key name if you already have one.
|
||||
app_config->set_bool("preset_bundle_auto_update", enabled ? true : false);
|
||||
app_config->save();
|
||||
} else if (strCmd == "close_page") {
|
||||
this->EndModal(wxID_CANCEL);
|
||||
} else if (strCmd == "export_page") {
|
||||
wxGetApp().CallAfter([this]() {
|
||||
ExportPresetBundleDialog dlg(this);
|
||||
dlg.ShowModal();
|
||||
});
|
||||
} else if (strCmd == "top_row_menu_action") {
|
||||
if (j["action"] == "open_folder") {
|
||||
std::string id = j["bundle_id"];
|
||||
OpenFolder(id);
|
||||
} else if (j["action"] == "delete_bundle") {
|
||||
std::string id = j["bundle_id"];
|
||||
DeleteBundle(id);
|
||||
} else if (j["action"] == "unsubscribe_bundle") {
|
||||
std::string id = j["bundle_id"];
|
||||
UnsubscribeBundle(id);
|
||||
}
|
||||
} else if (strCmd == "open_bundle_on_cloud") {
|
||||
std::string bundle_id = j["bundle_id"];
|
||||
OpenBundleOnCloud(bundle_id);
|
||||
}
|
||||
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "PresetBundleDialog::OnScriptMessage;Error:" << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// call on dialog create to populate the js local store
|
||||
void PresetBundleDialog::ListBundles()
|
||||
{
|
||||
json res;
|
||||
res["command"] = "list_bundles";
|
||||
res["sequence_id"] = "2000";
|
||||
res["data"] = json::array();
|
||||
|
||||
const auto& all_bundles = bundle_copy;
|
||||
|
||||
auto strip_prefix = [](const std::vector<std::string>& names) {
|
||||
json arr = json::array();
|
||||
for (const auto& name : names)
|
||||
arr.push_back(boost::filesystem::path(name).filename().string());
|
||||
return arr;
|
||||
};
|
||||
|
||||
for (const auto& bundle : all_bundles) {
|
||||
const auto& metadata = bundle.second;
|
||||
json temp;
|
||||
temp["id"] = metadata.id;
|
||||
temp["name"] = metadata.name;
|
||||
temp["type"] = metadata.bundle_type == Subscribed ? "Subscribed" : metadata.bundle_type == Local ? "Local" : "Default";
|
||||
temp["author"] = metadata.author;
|
||||
temp["version"] = metadata.version;
|
||||
temp["description"] = metadata.description;
|
||||
temp["path"] = metadata.path;
|
||||
|
||||
temp["printers"] = strip_prefix(metadata.printer_presets);
|
||||
temp["filaments"] = strip_prefix(metadata.filament_presets);
|
||||
temp["presets"] = strip_prefix(metadata.print_presets);
|
||||
|
||||
temp["update_available"] = metadata.update_available;
|
||||
temp["unauthorized"] = metadata.unauthorized;
|
||||
res["auto_update_enabled"] = app_config->get_bool("preset_bundle_auto_update");
|
||||
|
||||
res["data"].push_back(std::move(temp));
|
||||
}
|
||||
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", wxString::FromUTF8(res.dump(-1, ' ', false, json::error_handler_t::ignore)));
|
||||
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
|
||||
}
|
||||
|
||||
void PresetBundleDialog::OpenFolder(const std::string& id)
|
||||
{
|
||||
wxGetApp().preset_bundle->bundles.ReadLock();
|
||||
wxString target = _L(wxGetApp().preset_bundle->bundles.m_bundles.find(id)->second.path);
|
||||
wxGetApp().preset_bundle->bundles.ReadUnlock();
|
||||
wxFileName fn(target);
|
||||
if (fn.FileExists())
|
||||
target = fn.GetPath();
|
||||
|
||||
if (target.empty() || !wxFileName::DirExists(target)) {
|
||||
wxMessageBox(_L("Bundle folder does not exist."), _L("Open Folder"), wxOK | wxICON_WARNING, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wxLaunchDefaultApplication(target)) {
|
||||
wxMessageBox(_L("Failed to open folder."), _L("Open Folder"), wxOK | wxICON_ERROR, this);
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundleDialog::DeleteBundle(const std::string& id)
|
||||
{
|
||||
if (id.empty())
|
||||
return;
|
||||
|
||||
const int rc = wxMessageBox(_L("Delete selected bundle from folder and all presets loaded from it?"), _L("Delete Bundle"),
|
||||
wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
|
||||
if (rc != wxYES)
|
||||
return;
|
||||
|
||||
if (!DeleteBundleById(id)) {
|
||||
wxMessageBox(_L("Failed to remove bundle."), _L("Remove Bundle"), wxOK | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
wxGetApp().mainframe->update_side_preset_ui();
|
||||
}
|
||||
|
||||
void PresetBundleDialog::UnsubscribeBundle(const std::string& id)
|
||||
{
|
||||
if (id.empty())
|
||||
return;
|
||||
|
||||
const int rc = wxMessageBox(_L("Unsubscribe bundle?"), _L("UnsubscribeBundle"), wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
|
||||
if (rc != wxYES)
|
||||
return;
|
||||
|
||||
if (!UnsubscribeBundleById(id)) {
|
||||
wxMessageBox(_L("Failed to unsubscribe bundle."), _L("Unsubscribe Bundle"), wxOK | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
wxGetApp().mainframe->update_side_preset_ui();
|
||||
}
|
||||
|
||||
void PresetBundleDialog::OpenBundleOnCloud(const std::string& id)
|
||||
{
|
||||
if (id.empty())
|
||||
return;
|
||||
|
||||
if (!wxGetApp().getAgent())
|
||||
return;
|
||||
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(wxGetApp().getAgent()->get_cloud_agent());
|
||||
if (!orca_agent)
|
||||
return;
|
||||
|
||||
wxLaunchDefaultBrowser(wxString::FromUTF8(orca_agent->get_bundle_url(id)));
|
||||
}
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,102 @@
|
||||
#ifndef slic3r_PresetBundleDialog_hpp_
|
||||
#define slic3r_PresetBundleDialog_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <boost/thread/detail/thread.hpp>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
#include <memory>
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/language.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/fswatcher.h>
|
||||
#include <wx/webview.h>
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
#define DESIGN_GRAY900_COLOR wxColour("#363636") // Label color
|
||||
#define DESIGN_GRAY600_COLOR wxColour("#ACACAC") // Dimmed text color
|
||||
|
||||
#define DESIGN_WINDOW_SIZE wxSize(FromDIP(640), FromDIP(640))
|
||||
#define DESIGN_TITLE_SIZE wxSize(FromDIP(280), -1)
|
||||
#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
|
||||
#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
|
||||
#define DESIGN_INPUT_SIZE wxSize(FromDIP(120), -1)
|
||||
#define DESIGN_LEFT_MARGIN 25
|
||||
#define VERTICAL_GAP_SIZE FromDIP(4)
|
||||
class PresetBundleDialog : public Slic3r::GUI::DPIDialog
|
||||
{
|
||||
public:
|
||||
PresetBundleDialog(wxWindow* parent,
|
||||
wxWindowID id = wxID_ANY,
|
||||
const wxString& title = wxT(""),
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX);
|
||||
|
||||
~PresetBundleDialog();
|
||||
|
||||
void create();
|
||||
|
||||
bool DeleteBundleById(const wxString& id);
|
||||
bool UnsubscribeBundleById(const std::string& id);
|
||||
|
||||
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
|
||||
bool recreate_GUI() const { return m_recreate_GUI; }
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
// webview utilities
|
||||
void load_url(wxString& url);
|
||||
void ListBundles();
|
||||
void OpenFolder(const std::string& id);
|
||||
void DeleteBundle(const std::string& id);
|
||||
void UnsubscribeBundle(const std::string& id);
|
||||
void OpenBundleOnCloud(const std::string& id);
|
||||
|
||||
void OnPresetBundlePage();
|
||||
|
||||
// sends command to webview
|
||||
void RunScript(const wxString& s);
|
||||
|
||||
// webview events
|
||||
void OnScriptMessage(wxWebViewEvent& e);
|
||||
|
||||
void StartDialogWorker();
|
||||
void StopDialogWorker();
|
||||
|
||||
void RefreshBundleMap();
|
||||
|
||||
bool CheckUpdateCloud();
|
||||
|
||||
void OnBundleUpdate(wxCommandEvent& evt);
|
||||
|
||||
// true if b>a
|
||||
bool CompareVer(const std::string& a, const std::string& b);
|
||||
|
||||
protected:
|
||||
wxFileSystemWatcher* m_watcher = nullptr;
|
||||
|
||||
void OnFSWatch(wxFileSystemWatcherEvent& e);
|
||||
|
||||
bool m_seq_top_layer_only_changed{false};
|
||||
bool m_recreate_GUI{false};
|
||||
|
||||
// Webview
|
||||
wxWebView* m_browser{nullptr};
|
||||
std::unordered_map<std::string, BundleMetadata> bundle_copy;
|
||||
|
||||
boost::thread m_dialog_worker_thread;
|
||||
std::shared_ptr<int> m_dialog_worker_token;
|
||||
std::atomic<bool> m_check_update_pending{false};
|
||||
std::atomic<bool> m_update_bundles{false};
|
||||
|
||||
private:
|
||||
Slic3r::AppConfig* app_config;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
#endif
|
||||
@@ -1133,16 +1133,21 @@ void PlaterPresetComboBox::update()
|
||||
std::map<wxString, wxBitmap*> nonsys_presets;
|
||||
//BBS: add project embedded presets logic
|
||||
std::map<wxString, wxBitmap*> project_embedded_presets;
|
||||
// ORCA: add bundle presets
|
||||
std::map<wxString, wxBitmap*> bundle_presets;
|
||||
std::map<wxString, wxBitmap *> system_presets;
|
||||
std::map<wxString, wxBitmap *> uncompatible_presets;
|
||||
std::unordered_set<std::string> system_printer_models;
|
||||
std::map<wxString, wxString> preset_descriptions;
|
||||
std::map<wxString, std::string> preset_filament_vendors;
|
||||
std::map<wxString, std::string> preset_filament_types;
|
||||
std::map<wxString, std::string> preset_filament_names; // ORCA
|
||||
std::map<wxString, std::string> preset_aliases; // ORCA
|
||||
std::map<wxString, std::string> preset_bundle_ids;
|
||||
std::map<wxString, std::string> preset_bundle_names;
|
||||
//BBS: move system to the end
|
||||
wxString selected_system_preset;
|
||||
wxString selected_user_preset;
|
||||
wxString selected_bundle_preset;
|
||||
wxString tooltip;
|
||||
const std::deque<Preset>& presets = m_collection->get_presets();
|
||||
|
||||
@@ -1170,7 +1175,21 @@ void PlaterPresetComboBox::update()
|
||||
}
|
||||
|
||||
bool single_bar = false;
|
||||
wxString name = get_preset_name(preset);
|
||||
wxString name = preset.name;
|
||||
preset_aliases[name] = get_preset_name(preset).ToStdString(); // ORCA
|
||||
|
||||
// Track bundle names for bundled presets
|
||||
if (preset.is_from_bundle()) {
|
||||
m_preset_bundle->bundles.ReadLock();
|
||||
auto bundle_it = m_preset_bundle->bundles.m_bundles.find(preset.bundle_id);
|
||||
if (bundle_it != m_preset_bundle->bundles.m_bundles.end()) {
|
||||
preset_bundle_ids[name] = bundle_it->second.id;
|
||||
preset_bundle_names[name] = bundle_it->second.name;
|
||||
}
|
||||
m_preset_bundle->bundles.ReadUnlock();
|
||||
|
||||
}
|
||||
|
||||
if (m_type == Preset::TYPE_FILAMENT)
|
||||
{
|
||||
#if 0
|
||||
@@ -1192,7 +1211,6 @@ void PlaterPresetComboBox::update()
|
||||
if (preset_filament_vendors[name] == "Bambu Lab")
|
||||
preset_filament_vendors[name] = "Bambu";
|
||||
preset_filament_types[name] = preset.config.option<ConfigOptionStrings>("filament_type")->values.at(0);
|
||||
preset_filament_names[name] = name.ToStdString(); // ORCA
|
||||
//}
|
||||
}
|
||||
wxBitmap* bmp = get_bmp(preset);
|
||||
@@ -1214,6 +1232,7 @@ void PlaterPresetComboBox::update()
|
||||
name = from_u8(is_selected && preset.is_dirty ? Preset::suffix_modified() + printer_model : printer_model);
|
||||
|
||||
if (system_printer_models.count(printer_model) == 0) {
|
||||
preset_aliases[name] = name.ToStdString(); // ORCA
|
||||
system_presets.emplace(name, bmp);
|
||||
system_printer_models.insert(printer_model);
|
||||
}
|
||||
@@ -1246,6 +1265,15 @@ void PlaterPresetComboBox::update()
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
// ORCA: add bundle presets
|
||||
else if (preset.is_from_bundle())
|
||||
{
|
||||
bundle_presets.emplace(name, bmp);
|
||||
if (is_selected) {
|
||||
selected_bundle_preset = name;
|
||||
tooltip = get_tooltip(preset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonsys_presets.emplace(name, bmp);
|
||||
@@ -1271,11 +1299,11 @@ void PlaterPresetComboBox::update()
|
||||
"Bambu PLA Galaxy", "Bambu PLA Metal", "Bambu PLA Marble", "Bambu PETG-CF", "Bambu PETG Translucent", "Bambu ABS-GF"};
|
||||
std::vector<std::string> first_vendors = {"", "Bambu", "Generic"}; // Empty vendor for non-system presets
|
||||
std::vector<std::string> first_types = {"PLA", "PETG", "ABS", "TPU"};
|
||||
auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &preset_filament_names, &first_types, &selected_in_ams]
|
||||
auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &preset_aliases, &preset_bundle_ids, &preset_bundle_names, &first_types, &selected_in_ams]
|
||||
(std::map<wxString, wxBitmap *> const &presets, wxString const &selected, std::string const &group, wxString const &groupName) {
|
||||
if (!presets.empty()) {
|
||||
set_label_marker(Append(_L(group), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
if (m_type == Preset::TYPE_FILAMENT) {
|
||||
if (m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_PRINTER) {
|
||||
std::vector<std::map<wxString, wxBitmap *>::value_type const*> list(presets.size(), nullptr);
|
||||
std::transform(presets.begin(), presets.end(), list.begin(), [](auto & pair) { return &pair; });
|
||||
bool groupByGroup = group != "System presets";
|
||||
@@ -1283,7 +1311,7 @@ void PlaterPresetComboBox::update()
|
||||
// if (GetCount() == 1) Clear();
|
||||
// else SetString(GetCount() - 1, "");
|
||||
//}
|
||||
if (group == "System presets" || group == "Unsupported presets")
|
||||
if (m_type == Preset::TYPE_FILAMENT && (group == "System presets" || group == "Unsupported presets"))
|
||||
std::sort(list.begin(), list.end(), [&filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &first_types](auto *l, auto *r) {
|
||||
{ // Compare order
|
||||
auto iter1 = std::find(filament_orders.begin(), filament_orders.end(), l->first);
|
||||
@@ -1306,10 +1334,11 @@ void PlaterPresetComboBox::update()
|
||||
return l->first < r->first;
|
||||
});
|
||||
// ORCA add sorting support for vendor / type for user presets. also non grouped items
|
||||
if (groupName == "by_vendor" || groupName == "by_type" || groupName == ""){
|
||||
auto by = groupName == "by_vendor" ? preset_filament_vendors
|
||||
if (groupName == "by_bundle" || groupName == "by_vendor" || groupName == "by_type" || groupName == ""){
|
||||
auto by = groupName == "by_bundle" ? preset_bundle_names
|
||||
: groupName == "by_vendor" ? preset_filament_vendors
|
||||
: groupName == "by_type" ? preset_filament_types
|
||||
: preset_filament_names;
|
||||
: preset_aliases;
|
||||
std::sort(list.begin(), list.end(), [&by](auto *l, auto *r) {
|
||||
auto get_key = [&](auto* item) -> std::pair<bool, std::string> {
|
||||
std::string str = by.count(item->first) ? by.at(item->first) : "";
|
||||
@@ -1319,20 +1348,29 @@ void PlaterPresetComboBox::update()
|
||||
auto [l_valid, l_lower] = get_key(l);
|
||||
auto [r_valid, r_lower] = get_key(r);
|
||||
return (l_valid != r_valid) ? l_valid > r_valid
|
||||
: (l_lower != r_lower) ? l_lower < r_lower
|
||||
: (l_lower != r_lower) ? l_lower < r_lower
|
||||
: l->first < r->first;
|
||||
});
|
||||
}
|
||||
bool unsupported = group == "Unsupported presets";
|
||||
for (auto it : list) {
|
||||
// ORCA add sorting support for vendor / type for user presets
|
||||
auto groupName2 = groupName == "by_type" ? (preset_filament_types[it->first].empty() ? _L("Unspecified") : preset_filament_types[it->first])
|
||||
: groupName == "by_vendor" ? (preset_filament_vendors[it->first].empty() ? _L("Unspecified") : preset_filament_vendors[it->first])
|
||||
: groupByGroup ? groupName
|
||||
auto groupName2 = groupName == "by_bundle" ? (preset_bundle_names[it->first].empty() ? _L("Unspecified") : preset_bundle_names[it->first])
|
||||
: groupName == "by_type" ? (preset_filament_types[it->first].empty() ? _L("Unspecified") : preset_filament_types[it->first])
|
||||
: groupName == "by_vendor" ? (preset_filament_vendors[it->first].empty() ? _L("Unspecified") : preset_filament_vendors[it->first])
|
||||
: groupByGroup ? groupName
|
||||
: preset_filament_vendors[it->first];
|
||||
int index = Append(it->first, *it->second, groupName2, nullptr, unsupported ? DD_ITEM_STYLE_DISABLED : 0);
|
||||
int index = groupName == "by_bundle"
|
||||
? Append(preset_aliases[it->first], *it->second,
|
||||
from_u8(preset_bundle_ids[it->first]), groupName2, nullptr,
|
||||
unsupported ? DD_ITEM_STYLE_DISABLED : 0)
|
||||
: Append(preset_aliases[it->first], *it->second, groupName2, nullptr,
|
||||
unsupported ? DD_ITEM_STYLE_DISABLED : 0);
|
||||
SetItemAlias(index, it->first);
|
||||
if (unsupported)
|
||||
set_label_marker(index, LABEL_ITEM_DISABLED);
|
||||
else if (m_type == Preset::TYPE_PRINTER && group == "System presets" )
|
||||
set_label_marker(index, LABEL_ITEM_PRINTER_MODELS);
|
||||
SetItemTooltip(index, preset_descriptions[it->first]);
|
||||
bool is_selected = it->first == selected;
|
||||
validate_selection(is_selected);
|
||||
@@ -1342,7 +1380,9 @@ void PlaterPresetComboBox::update()
|
||||
}
|
||||
} else {
|
||||
for (std::map<wxString, wxBitmap *>::const_iterator it = presets.begin(); it != presets.end(); ++it) {
|
||||
SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]);
|
||||
int index = Append(preset_aliases[it->first], *it->second);
|
||||
SetItemAlias(index, it->first);
|
||||
SetItemTooltip(index, preset_descriptions[it->first]);
|
||||
if (group == "System presets")
|
||||
set_label_marker(GetCount() - 1, LABEL_ITEM_PRINTER_MODELS);
|
||||
validate_selection(it->first == selected);
|
||||
@@ -1360,6 +1400,9 @@ void PlaterPresetComboBox::update()
|
||||
: group_filament_presets == "3" ? "by_vendor" // Create sub menus with filament vendor
|
||||
: ""; // Use without sub menu
|
||||
add_presets(nonsys_presets, selected_user_preset, L("User presets"), group_filament_presets_by);
|
||||
// ORCA: add bundle presets with sub-dropdown grouping for filament and printer
|
||||
auto bundle_group_name = (m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_PRINTER) ? "by_bundle" : "";
|
||||
add_presets(bundle_presets, selected_bundle_preset, L("Bundle presets"), bundle_group_name);
|
||||
// BBS: move system to the end
|
||||
add_presets(system_presets, selected_system_preset, L("System presets"), _L("System"));
|
||||
add_presets(uncompatible_presets, {}, L("Unsupported presets"), _L("Unsupported") + " ");
|
||||
@@ -1579,7 +1622,10 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt)
|
||||
|
||||
wxString TabPresetComboBox::get_preset_name(const Preset& preset)
|
||||
{
|
||||
return from_u8(preset.label(true));
|
||||
if (preset.is_from_bundle())
|
||||
return from_u8(preset.label(false));
|
||||
else
|
||||
return from_u8(preset.label(true));
|
||||
}
|
||||
|
||||
// Update the choice UI from the list of presets.
|
||||
@@ -1598,7 +1644,12 @@ void TabPresetComboBox::update()
|
||||
std::map<wxString, std::pair<wxBitmap*, bool>> project_embedded_presets;
|
||||
//BBS: move system to the end
|
||||
std::map<wxString, std::pair<wxBitmap*, bool>> system_presets;
|
||||
// ORCA: add bundle presets
|
||||
std::map<wxString, std::pair<wxBitmap*, bool>> bundle_presets;
|
||||
std::map<wxString, wxString> preset_descriptions;
|
||||
std::map<wxString, std::string> preset_aliases; // ORCA
|
||||
std::map<wxString, std::string> preset_bundle_ids;
|
||||
std::map<wxString, std::string> preset_bundle_names;
|
||||
|
||||
wxString selected = "";
|
||||
//BBS: move system to the end
|
||||
@@ -1625,10 +1676,23 @@ void TabPresetComboBox::update()
|
||||
wxBitmap* bmp = get_bmp(preset);
|
||||
assert(bmp);
|
||||
|
||||
const wxString name = get_preset_name(preset);
|
||||
const wxString name = preset.name;
|
||||
preset_aliases[name] = get_preset_name(preset).ToStdString();
|
||||
if (preset.is_system)
|
||||
preset_descriptions.emplace(name, from_u8(preset.description));
|
||||
|
||||
// ORCA: Track bundle names for bundled presets
|
||||
if (preset.is_from_bundle()) {
|
||||
m_preset_bundle->bundles.ReadLock();
|
||||
auto bundle_it = m_preset_bundle->bundles.m_bundles.find(preset.bundle_id);
|
||||
if (bundle_it != m_preset_bundle->bundles.m_bundles.end()) {
|
||||
preset_bundle_ids[name] = bundle_it->second.id;
|
||||
preset_bundle_names[name] = bundle_it->second.name;
|
||||
}
|
||||
m_preset_bundle->bundles.ReadUnlock();
|
||||
|
||||
}
|
||||
|
||||
if (preset.is_default || preset.is_system) {
|
||||
//BBS: move system to the end
|
||||
system_presets.emplace(name, std::pair<wxBitmap *, bool>(bmp, is_enabled));
|
||||
@@ -1647,6 +1711,13 @@ void TabPresetComboBox::update()
|
||||
if (i == idx_selected)
|
||||
selected = name;
|
||||
}
|
||||
// ORCA: add bundle presets
|
||||
else if (preset.is_from_bundle())
|
||||
{
|
||||
bundle_presets.emplace(name, std::pair<wxBitmap*, bool>(bmp, is_enabled));
|
||||
if (i == idx_selected)
|
||||
selected = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::pair<wxBitmap*, bool> pair(bmp, is_enabled);
|
||||
@@ -1680,6 +1751,27 @@ void TabPresetComboBox::update()
|
||||
set_label_marker(Append(_L("User presets"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
int item_id = Append(it->first, *it->second.first);
|
||||
SetItemAlias(item_id, it->first);
|
||||
SetItemTooltip(item_id, preset_descriptions[it->first]);
|
||||
bool is_enabled = it->second.second;
|
||||
if (!is_enabled)
|
||||
set_label_marker(item_id, LABEL_ITEM_DISABLED);
|
||||
validate_selection(it->first == selected);
|
||||
}
|
||||
}
|
||||
// ORCA: add bundle presets with sub-dropdown grouping
|
||||
if (!bundle_presets.empty())
|
||||
{
|
||||
set_label_marker(Append(_L("Bundle presets"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = bundle_presets.begin(); it != bundle_presets.end(); ++it) {
|
||||
// Get bundle name for grouping
|
||||
wxString bundle_name = _L("Unspecified");
|
||||
if (preset_bundle_names.count(it->first) > 0 && !preset_bundle_names[it->first].empty()) {
|
||||
bundle_name = preset_bundle_names[it->first];
|
||||
}
|
||||
// Use Append with group parameter for sub-dropdown grouping
|
||||
int item_id = Append(preset_aliases[it->first], *it->second.first, from_u8(preset_bundle_ids[it->first]), bundle_name);
|
||||
SetItemAlias(item_id, it->first);
|
||||
SetItemTooltip(item_id, preset_descriptions[it->first]);
|
||||
bool is_enabled = it->second.second;
|
||||
if (!is_enabled)
|
||||
@@ -1693,6 +1785,7 @@ void TabPresetComboBox::update()
|
||||
set_label_marker(Append(_L("System presets"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
|
||||
for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = system_presets.begin(); it != system_presets.end(); ++it) {
|
||||
int item_id = Append(it->first, *it->second.first);
|
||||
SetItemAlias(item_id, it->first);
|
||||
SetItemTooltip(item_id, preset_descriptions[it->first]);
|
||||
bool is_enabled = it->second.second;
|
||||
if (!is_enabled)
|
||||
|
||||
@@ -36,7 +36,7 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox
|
||||
m_presets = tab->get_presets();
|
||||
|
||||
const Preset &sel_preset = m_presets->get_selected_preset();
|
||||
std::string preset_name = sel_preset.is_default ? "Untitled" : sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : sel_preset.name;
|
||||
std::string preset_name = sel_preset.is_default ? "Untitled" : sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : sel_preset.is_from_bundle() && !sel_preset.alias.empty() ? sel_preset.alias : sel_preset.name;
|
||||
|
||||
// if name contains extension
|
||||
if (boost::iends_with(preset_name, ".ini")) {
|
||||
@@ -162,7 +162,7 @@ void SavePresetDialog::Item::update()
|
||||
}
|
||||
|
||||
const Preset *existing = m_presets->find_preset(m_preset_name, false);
|
||||
if (m_valid_type == Valid && existing && (existing->is_default || existing->is_system)) {
|
||||
if (m_valid_type == Valid && existing && !existing->can_overwrite()) {
|
||||
info_line = _L("Overwriting a system profile is not allowed.");
|
||||
m_valid_type = NoValid;
|
||||
}
|
||||
@@ -230,13 +230,11 @@ void SavePresetDialog::Item::update_valid_bmp()
|
||||
void SavePresetDialog::Item::accept()
|
||||
{
|
||||
if (m_valid_type == Warning) {
|
||||
// BBS add sync info
|
||||
auto it = m_presets->find_preset(m_preset_name, false);
|
||||
Preset ¤t_preset = *it;
|
||||
current_preset.sync_info = "delete";
|
||||
if (!current_preset.setting_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "delete preset = " << current_preset.name << ", setting_id = " << current_preset.setting_id;
|
||||
wxGetApp().delete_preset_from_cloud(current_preset.setting_id);
|
||||
wxGetApp().delete_preset_from_cloud(current_preset.setting_id, current_preset.file);
|
||||
}
|
||||
m_presets->delete_preset(m_preset_name);
|
||||
}
|
||||
|
||||
@@ -2601,12 +2601,13 @@ void SelectMachineDialog::clear_ip_address_config(wxCommandEvent& e)
|
||||
void SelectMachineDialog::update_user_machine_list()
|
||||
{
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
if (m_agent && m_agent->is_user_login()) {
|
||||
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (m_agent && m_agent->is_user_login(provider)) {
|
||||
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = agent->get_user_print_info(&http_code, &body);
|
||||
int result = agent->get_user_print_info(&http_code, &body, provider);
|
||||
CallAfter([token, this, result, body] {
|
||||
if (token.expired()) {return;}
|
||||
if (result == 0) {
|
||||
@@ -3233,7 +3234,7 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
|
||||
|
||||
/** error check **/
|
||||
/* check cloud machine connections */
|
||||
if (!obj_->is_lan_mode_printer() && !agent->is_server_connected()) {
|
||||
if (!obj_->is_lan_mode_printer() && !agent->is_server_connected(wxGetApp().get_printer_cloud_provider())) {
|
||||
show_status(PrintDialogStatus::PrintStatusConnectingServer);
|
||||
reset_timeout();
|
||||
return;
|
||||
@@ -3770,7 +3771,7 @@ void SelectMachineDialog::set_default()
|
||||
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (agent) {
|
||||
if (agent->is_user_login()) {
|
||||
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
|
||||
show_status(PrintDialogStatus::PrintStatusInit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ MachineObjectPanel::~MachineObjectPanel() {}
|
||||
|
||||
void MachineObjectPanel::show_bind_dialog()
|
||||
{
|
||||
if (wxGetApp().is_user_login()) {
|
||||
if (wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider())) {
|
||||
BindMachineDialog dlg;
|
||||
dlg.update_machine_info(m_info);
|
||||
dlg.ShowModal();
|
||||
@@ -387,13 +387,14 @@ void SelectMachinePopup::Popup(wxWindow *WXUNUSED(focus))
|
||||
m_refresh_timer->Start(MACHINE_LIST_REFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
if (wxGetApp().is_user_login()) {
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (wxGetApp().is_user_login(provider)) {
|
||||
if (!get_print_info_thread) {
|
||||
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
|
||||
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = agent->get_user_print_info(&http_code, &body);
|
||||
int result = agent->get_user_print_info(&http_code, &body, provider);
|
||||
CallAfter([token, this, result, body]() {
|
||||
if (token.expired()) {return;}
|
||||
if (result == 0) {
|
||||
@@ -511,7 +512,7 @@ void SelectMachinePopup::update_other_devices()
|
||||
/* do not show printer bind state is empty */
|
||||
if (!mobj->is_avaliable()) continue;
|
||||
|
||||
if (!wxGetApp().is_user_login() && !mobj->is_lan_mode_printer())
|
||||
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider()) && !mobj->is_lan_mode_printer())
|
||||
continue;
|
||||
|
||||
/* do not show printer in my list */
|
||||
@@ -983,7 +984,7 @@ void EditDevNameDialog::on_edit_name(wxCommandEvent &e)
|
||||
auto utf8_str = new_dev_name.ToUTF8();
|
||||
auto name = std::string(utf8_str.data(), utf8_str.length());
|
||||
if (m_info)
|
||||
dev->modify_device_name(m_info->get_dev_id(), name);
|
||||
dev->modify_device_name(m_info->get_dev_id(), name, wxGetApp().get_printer_cloud_provider());
|
||||
}
|
||||
DPIDialog::EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
@@ -1013,12 +1013,13 @@ void SendToPrinterDialog::clear_ip_address_config(wxCommandEvent& e)
|
||||
void SendToPrinterDialog::update_user_machine_list()
|
||||
{
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
if (m_agent && m_agent->is_user_login()) {
|
||||
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (m_agent && m_agent->is_user_login(provider)) {
|
||||
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = agent->get_user_print_info(&http_code, &body);
|
||||
int result = agent->get_user_print_info(&http_code, &body, provider);
|
||||
CallAfter([token, this, result, body] {
|
||||
if (token.expired()) {return;}
|
||||
if (result == 0) {
|
||||
@@ -1224,11 +1225,12 @@ void SendToPrinterDialog::update_show_status()
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!agent) return;
|
||||
if (!dev) return;
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
MachineObject* obj_ = dev->get_my_machine(m_printer_last_select);
|
||||
|
||||
if (!obj_) {
|
||||
if (agent) {
|
||||
if (agent->is_user_login()) {
|
||||
if (agent->is_user_login(provider)) {
|
||||
show_status(PrintDialogStatus::PrintStatusInvalidPrinter);
|
||||
}
|
||||
}
|
||||
@@ -1237,7 +1239,7 @@ void SendToPrinterDialog::update_show_status()
|
||||
|
||||
/* check cloud machine connections */
|
||||
if (!obj_->is_lan_mode_printer()) {
|
||||
if (!agent->is_server_connected()) {
|
||||
if (!agent->is_server_connected(provider)) {
|
||||
show_status(PrintDialogStatus::PrintStatusConnectingServer);
|
||||
reset_timeout();
|
||||
return;
|
||||
@@ -1564,7 +1566,7 @@ void SendToPrinterDialog::set_default()
|
||||
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (agent) {
|
||||
if (agent->is_user_login()) {
|
||||
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
|
||||
show_status(PrintDialogStatus::PrintStatusInit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2054,12 +2054,13 @@ void SyncAmsInfoDialog::Enable_Auto_Refill(bool enable)
|
||||
void SyncAmsInfoDialog::update_user_machine_list()
|
||||
{
|
||||
NetworkAgent *m_agent = wxGetApp().getAgent();
|
||||
if (m_agent && m_agent->is_user_login()) {
|
||||
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr(m_token)] {
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
if (m_agent && m_agent->is_user_login(provider)) {
|
||||
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr(m_token), provider] {
|
||||
NetworkAgent *agent = wxGetApp().getAgent();
|
||||
unsigned int http_code;
|
||||
std::string body;
|
||||
int result = agent->get_user_print_info(&http_code, &body);
|
||||
int result = agent->get_user_print_info(&http_code, &body, provider);
|
||||
CallAfter([token, this, result, body] {
|
||||
if (token.expired()) { return; }
|
||||
if (result == 0) {
|
||||
@@ -2307,13 +2308,14 @@ void SyncAmsInfoDialog::update_show_status()
|
||||
return;
|
||||
}
|
||||
if (!dev) return;
|
||||
const std::string provider = wxGetApp().get_printer_cloud_provider();
|
||||
|
||||
// blank plate has no valid gcode file
|
||||
if (is_must_finish_slice_then_connected_printer()) { return; }
|
||||
MachineObject * obj_ = dev->get_selected_machine();
|
||||
if (!obj_) {
|
||||
if (agent) {
|
||||
if (agent->is_user_login()) {
|
||||
if (agent->is_user_login(provider)) {
|
||||
show_status(PrintDialogStatus::PrintStatusInvalidPrinter);
|
||||
}
|
||||
}
|
||||
@@ -2322,7 +2324,7 @@ void SyncAmsInfoDialog::update_show_status()
|
||||
|
||||
/* check cloud machine connections */
|
||||
if (!obj_->is_lan_mode_printer()) {
|
||||
if (!agent->is_server_connected()) {
|
||||
if (!agent->is_server_connected(provider)) {
|
||||
show_status(PrintDialogStatus::PrintStatusConnectingServer);
|
||||
reset_timeout();
|
||||
return;
|
||||
@@ -2585,7 +2587,7 @@ void SyncAmsInfoDialog::set_default(bool hide_some)
|
||||
NetworkAgent *agent = wxGetApp().getAgent();
|
||||
if (agent) {
|
||||
if (!hide_some) {
|
||||
if (agent->is_user_login()) {
|
||||
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
|
||||
show_status(PrintDialogStatus::PrintStatusInit);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-11
@@ -229,9 +229,17 @@ void Tab::create_preset_tab()
|
||||
if (m_type == Preset::TYPE_PRINTER && !m_presets_choice->is_selected_physical_printer())
|
||||
m_preset_bundle->physical_printers.unselect_printer();
|
||||
|
||||
// select preset
|
||||
std::string preset_name = m_presets_choice->GetString(selection).ToUTF8().data();
|
||||
select_preset(Preset::remove_suffix_modified(preset_name));
|
||||
// select preset — prefer stored internal name to avoid alias collisions
|
||||
wxString stored_name = m_presets_choice->GetItemAlias(selection);
|
||||
std::string preset_name;
|
||||
if (!stored_name.empty()) {
|
||||
preset_name = Preset::remove_suffix_modified(stored_name.ToUTF8().data());
|
||||
} else {
|
||||
std::string selected_label = Preset::remove_suffix_modified(
|
||||
m_presets_choice->GetString(selection).ToUTF8().data());
|
||||
preset_name = m_preset_bundle->get_preset_name_by_alias(m_type, selected_label);
|
||||
}
|
||||
select_preset(preset_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5670,7 +5678,7 @@ void Tab::update_btns_enabling()
|
||||
// and any user preset
|
||||
const Preset& preset = m_presets->get_edited_preset();
|
||||
m_btn_delete_preset->Show((m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection())
|
||||
|| (!preset.is_default && !preset.is_system));
|
||||
|| preset.can_overwrite());
|
||||
|
||||
//if (m_btn_edit_ph_printer)
|
||||
// m_btn_edit_ph_printer->SetToolTip( m_preset_bundle->physical_printers.has_selection() ?
|
||||
@@ -5851,7 +5859,7 @@ bool Tab::select_preset(
|
||||
Preset ¤t_preset = m_presets->get_selected_preset();
|
||||
|
||||
// Obtain compatible filament and process presets for printers
|
||||
if (m_preset_bundle && m_presets->get_preset_base(current_preset) == ¤t_preset && printer_tab && !current_preset.is_system) {
|
||||
if (m_preset_bundle && m_presets->get_preset_base(current_preset) == ¤t_preset && printer_tab && !current_preset.is_system && !current_preset.is_from_bundle()) {
|
||||
delete_third_printer = true;
|
||||
for (const Preset &preset : m_preset_bundle->filaments.get_presets()) {
|
||||
if (preset.is_compatible && !preset.is_default) {
|
||||
@@ -5859,7 +5867,6 @@ bool Tab::select_preset(
|
||||
filament_presets.push_front(preset);
|
||||
else
|
||||
filament_presets.push_back(preset);
|
||||
if (!preset.setting_id.empty()) { m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); }
|
||||
}
|
||||
}
|
||||
for (const Preset &preset : m_preset_bundle->prints.get_presets()) {
|
||||
@@ -5868,13 +5875,11 @@ bool Tab::select_preset(
|
||||
process_presets.push_front(preset);
|
||||
else
|
||||
process_presets.push_back(preset);
|
||||
if (!preset.setting_id.empty()) { m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!current_preset.setting_id.empty()) {
|
||||
m_presets->set_sync_info_and_save(current_preset.name, current_preset.setting_id, "delete", 0);
|
||||
wxGetApp().delete_preset_from_cloud(current_preset.setting_id);
|
||||
wxGetApp().delete_preset_from_cloud(current_preset.setting_id, current_preset.file);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "delete preset = " << current_preset.name << ", setting_id = " << current_preset.setting_id;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("will delete current preset...");
|
||||
@@ -5965,7 +5970,7 @@ bool Tab::select_preset(
|
||||
|
||||
for (const Preset &preset : filament_presets) {
|
||||
if (!preset.setting_id.empty()) {
|
||||
wxGetApp().delete_preset_from_cloud(preset.setting_id);
|
||||
wxGetApp().delete_preset_from_cloud(preset.setting_id, preset.file);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "delete filament preset = " << preset.name << ", setting_id = " << preset.setting_id;
|
||||
preset_bundle->filaments.delete_preset(preset.name);
|
||||
@@ -5973,7 +5978,7 @@ bool Tab::select_preset(
|
||||
|
||||
for (const Preset &preset : process_presets) {
|
||||
if (!preset.setting_id.empty()) {
|
||||
wxGetApp().delete_preset_from_cloud(preset.setting_id);
|
||||
wxGetApp().delete_preset_from_cloud(preset.setting_id, preset.file);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "delete print preset = " << preset.name << ", setting_id = " << preset.setting_id;
|
||||
preset_bundle->prints.delete_preset(preset.name);
|
||||
|
||||
@@ -437,6 +437,7 @@ protected:
|
||||
// return true if cancelled
|
||||
bool tree_sel_change_delayed(wxCommandEvent& event);
|
||||
void on_presets_changed();
|
||||
void update_printer_agent_if_needed();
|
||||
void build_preset_description_line(ConfigOptionsGroup* optgroup);
|
||||
void update_preset_description_line();
|
||||
void update_frequently_changed_parameters();
|
||||
|
||||
@@ -1087,7 +1087,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
|
||||
// for system/default/external presets we should take an edited name
|
||||
//BBS: add project embedded preset logic and refine is_external
|
||||
bool save_to_project = false;
|
||||
if (preset.is_system || preset.is_default) {
|
||||
if (!preset.can_overwrite()) {
|
||||
//if (preset.is_system || preset.is_default || preset.is_external) {
|
||||
SavePresetDialog save_dlg(this, preset.type);
|
||||
if (save_dlg.ShowModal() != wxID_OK) {
|
||||
@@ -1114,7 +1114,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
|
||||
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) {
|
||||
const Preset& preset = tab->get_presets()->get_edited_preset();
|
||||
//BBS: add project embedded preset logic and refine is_external
|
||||
if (preset.is_system || preset.is_default)
|
||||
if (!preset.can_overwrite())
|
||||
//if (preset.is_system || preset.is_default || preset.is_external)
|
||||
types_for_save.emplace_back(preset.type);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ int UserManager::parse_json(std::string payload)
|
||||
GUI::MessageDialog msgdialog(nullptr, _L("Log in successful."), "", wxAPPLY | wxOK);
|
||||
msgdialog.ShowModal();
|
||||
}
|
||||
dev->update_user_machine_list_info();
|
||||
dev->update_user_machine_list_info(GUI::wxGetApp().get_printer_cloud_provider());
|
||||
dev->set_selected_machine(dev_id);
|
||||
return 0;
|
||||
}
|
||||
@@ -75,4 +75,4 @@ int UserManager::parse_json(std::string payload)
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -509,7 +509,6 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
|
||||
SaveProfile();
|
||||
|
||||
std::string oldregion = m_ProfileJson["region"];
|
||||
bool bLogin = false;
|
||||
if (m_Region != oldregion) {
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
std::string country_code = config->get_country_code();
|
||||
@@ -517,7 +516,6 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
|
||||
if (agent) {
|
||||
agent->set_country_code(country_code);
|
||||
if (wxGetApp().is_user_login()) {
|
||||
bLogin = true;
|
||||
agent->user_logout();
|
||||
}
|
||||
}
|
||||
@@ -526,10 +524,7 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
|
||||
this->EndModal(wxID_OK);
|
||||
|
||||
if (InstallNetplugin)
|
||||
GUI::wxGetApp().CallAfter([this] { GUI::wxGetApp().ShowDownNetPluginDlg(); });
|
||||
|
||||
if (bLogin)
|
||||
GUI::wxGetApp().CallAfter([this] { login(); });
|
||||
GUI::wxGetApp().CallAfter([] { GUI::wxGetApp().ShowDownNetPluginDlg(); });
|
||||
}
|
||||
else if (strCmd == "user_guide_cancel") {
|
||||
this->EndModal(wxID_CANCEL);
|
||||
@@ -790,11 +785,9 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
|
||||
!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
|
||||
if (install_bundles.size() > 0) {
|
||||
// Install bundles from resources.
|
||||
// Don't create snapshot - we've already done that above if applicable.
|
||||
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
|
||||
return false;
|
||||
// If there are bundles to install, they will be installed by apply_vendor_config
|
||||
if (!install_bundles.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Will install " << install_bundles.size() << " vendor bundles from resources";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resource directory";
|
||||
}
|
||||
@@ -884,32 +877,82 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
|
||||
// Not switch filament
|
||||
//get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
|
||||
|
||||
//update the app_config
|
||||
app_config->set_section(AppConfig::SECTION_FILAMENTS, enabled_filaments);
|
||||
app_config->set_vendors(m_appconfig_new);
|
||||
// ORCA: functionality moved to PresetBundle::apply_vendor_config; keeping for future reference
|
||||
// // For each @System filament, check if a vendor-specific override exists
|
||||
// // in the loaded profiles. If so, replace the @System variant with the
|
||||
// // override (e.g. replace "Generic ABS @System" with BBL "Generic ABS").
|
||||
// // When printers from the default bundle are also selected, keep @System
|
||||
// // too since those printers need it.
|
||||
// static const std::string system_suffix = " @System";
|
||||
// auto it_default = enabled_vendors.find(PresetBundle::ORCA_DEFAULT_BUNDLE);
|
||||
// bool has_default_bundle_printer = it_default != enabled_vendors.end() && !it_default->second.empty();
|
||||
// bool has_filament_profiles = m_ProfileJson.contains("filament");
|
||||
|
||||
if (check_unsaved_preset_changes)
|
||||
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable,
|
||||
{preferred_model, preferred_variant, first_added_filament, std::string()});
|
||||
// // Check if any non-default vendor has selected printers
|
||||
// bool has_vendor_printer = false;
|
||||
// for (const auto& [vendor, models] : enabled_vendors) {
|
||||
// if (vendor != PresetBundle::ORCA_DEFAULT_BUNDLE && !models.empty()) {
|
||||
// has_vendor_printer = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// If the active filament is not in the wizard-selected filaments, switch to the first
|
||||
// compatible wizard-selected filament. This handles the first-run case where load_presets
|
||||
// falls back to "Generic PLA" even though the user selected a different filament.
|
||||
bool active_filament_selected = enabled_filaments.empty()
|
||||
|| enabled_filaments.count(preset_bundle->filament_presets.front()) > 0;
|
||||
if (!active_filament_selected) {
|
||||
for (const auto& [filament_name, _] : enabled_filaments) {
|
||||
const Preset* preset = preset_bundle->filaments.find_preset(filament_name);
|
||||
if (preset && preset->is_visible && preset->is_compatible) {
|
||||
preset_bundle->filaments.select_preset_by_name(filament_name, true);
|
||||
preset_bundle->filament_presets.front() = preset_bundle->filaments.get_selected_preset_name();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// std::map<std::string, std::string> supplemented_filaments;
|
||||
// for (const auto& [name, value] : enabled_filaments) {
|
||||
// if (name.size() > system_suffix.size() &&
|
||||
// name.compare(name.size() - system_suffix.size(), system_suffix.size(), system_suffix) == 0) {
|
||||
// std::string short_name = name.substr(0, name.size() - system_suffix.size());
|
||||
// if (has_vendor_printer && has_filament_profiles && m_ProfileJson["filament"].contains(short_name)) {
|
||||
// supplemented_filaments[short_name] = value;
|
||||
// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Replacing @System filament: '" << name << "' -> '" << short_name << "'";
|
||||
// if (has_default_bundle_printer) {
|
||||
// supplemented_filaments[name] = value;
|
||||
// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Also keeping '" << name << "' for default bundle printers";
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// supplemented_filaments[name] = value;
|
||||
// }
|
||||
|
||||
// Update the selections from the compatibilty.
|
||||
preset_bundle->export_selections(*app_config);
|
||||
// //update the app_config
|
||||
// app_config->set_section(AppConfig::SECTION_FILAMENTS, supplemented_filaments);
|
||||
// app_config->set_vendors(m_appconfig_new);
|
||||
|
||||
// if (check_unsaved_preset_changes)
|
||||
// preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable,
|
||||
// {preferred_model, preferred_variant, first_added_filament, std::string()});
|
||||
|
||||
// // If the active filament is not in the wizard-selected filaments, switch to the first
|
||||
// // compatible wizard-selected filament. This handles the first-run case where load_presets
|
||||
// // falls back to "Generic PLA" even though the user selected a different filament.
|
||||
// bool active_filament_selected = supplemented_filaments.empty()
|
||||
// || supplemented_filaments.count(preset_bundle->filament_presets.front()) > 0;
|
||||
// if (!active_filament_selected) {
|
||||
// for (const auto& [filament_name, _] : supplemented_filaments) {
|
||||
// const Preset* preset = preset_bundle->filaments.find_preset(filament_name);
|
||||
// if (preset && preset->is_visible && preset->is_compatible) {
|
||||
// preset_bundle->filaments.select_preset_by_name(filament_name, true);
|
||||
// preset_bundle->filament_presets.front() = preset_bundle->filaments.get_selected_preset_name();
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Update the selections from the compatibilty.
|
||||
// preset_bundle->export_selections(*app_config);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "calling apply_vendor_config from WebGuideDialog";
|
||||
// Call the Core library function to apply vendor configuration
|
||||
// This handles bundle installation, filament @System substitution, AppConfig updates, and preset loading
|
||||
if (!preset_bundle->apply_vendor_config(
|
||||
enabled_vendors,
|
||||
enabled_filaments,
|
||||
app_config,
|
||||
true,
|
||||
preferred_model,
|
||||
preferred_variant))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <wx/wfstream.h>
|
||||
|
||||
#include <boost/cast.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -40,13 +42,78 @@ END_EVENT_TABLE()
|
||||
|
||||
int ZUserLogin::web_sequence_id = 20000;
|
||||
|
||||
ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_ANY, "OrcaSlicer")
|
||||
namespace {
|
||||
|
||||
int reserve_loopback_port()
|
||||
{
|
||||
try {
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::ip::tcp::acceptor acceptor(io_service, {boost::asio::ip::tcp::v4(), 0});
|
||||
return static_cast<int>(acceptor.local_endpoint().port());
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string rewrite_loopback_url(std::string url, int port)
|
||||
{
|
||||
if (port <= 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const std::string old_port = std::to_string(LOCALHOST_PORT);
|
||||
const std::string new_port = std::to_string(port);
|
||||
|
||||
boost::replace_all(url, std::string(LOCALHOST_URL) + old_port, std::string(LOCALHOST_URL) + new_port);
|
||||
boost::replace_all(url, "http://127.0.0.1:" + old_port, "http://127.0.0.1:" + new_port);
|
||||
boost::replace_all(url, "http%3A%2F%2Flocalhost%3A" + old_port, "http%3A%2F%2Flocalhost%3A" + new_port);
|
||||
boost::replace_all(url, "http%3A%2F%2F127.0.0.1%3A" + old_port, "http%3A%2F%2F127.0.0.1%3A" + new_port);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int ZUserLogin::ensure_loopback_port()
|
||||
{
|
||||
if (m_loopback_port <= 0) {
|
||||
m_loopback_port = reserve_loopback_port();
|
||||
}
|
||||
int port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
|
||||
wxGetApp().start_http_server(port, m_cloud_agent->get_id());
|
||||
return port;
|
||||
}
|
||||
|
||||
ZUserLogin::ZUserLogin(std::shared_ptr<ICloudServiceAgent> cloud_agent)
|
||||
: wxDialog((wxWindow*) (wxGetApp().mainframe), wxID_ANY, "OrcaSlicer"), m_cloud_agent(cloud_agent)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
const auto bblnetwork_enabled =wxGetApp().app_config->get_bool("installed_networking");
|
||||
// Url
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (!agent && bblnetwork_enabled) {
|
||||
|
||||
if (!m_cloud_agent) {
|
||||
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);
|
||||
|
||||
auto* m_message = new wxStaticText(this, wxID_ANY,
|
||||
_L("Cloud agent is not available. Please restart OrcaSlicer and try again."),
|
||||
wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_message->SetForegroundColour(*wxBLACK);
|
||||
m_message->Wrap(FromDIP(360));
|
||||
m_sizer_main->Add(m_message, 0, wxALIGN_CENTER | wxALL, FromDIP(15));
|
||||
|
||||
m_sizer_main->Add(0, 0, 1, wxBOTTOM, 10);
|
||||
SetSizer(m_sizer_main);
|
||||
m_sizer_main->SetSizeHints(this);
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto bblnetwork_enabled = wxGetApp().app_config->get_bool("installed_networking");
|
||||
if (m_cloud_agent->get_id() == BBL_CLOUD_PROVIDER && !bblnetwork_enabled) {
|
||||
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
@@ -74,20 +141,16 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
}
|
||||
else {
|
||||
// Get the login URL from the cloud service agent
|
||||
} else {
|
||||
// Get the login URL from the injected cloud service agent
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
strlang.Replace("_", "-");
|
||||
TargetUrl = wxString::FromUTF8(agent->get_cloud_login_url(strlang.ToStdString()));
|
||||
m_networkOk = TargetUrl.StartsWith("file://");
|
||||
TargetUrl = wxString::FromUTF8(m_cloud_agent->get_cloud_login_url(strlang.ToStdString()));
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "login url = " << TargetUrl.ToStdString();
|
||||
|
||||
m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", wxGetApp().get_bbl_client_version());
|
||||
|
||||
// set the frame icon
|
||||
|
||||
// Create the webview
|
||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||
if (m_browser == nullptr) {
|
||||
@@ -97,12 +160,6 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
|
||||
m_browser->Hide();
|
||||
m_browser->SetSize(0, 0);
|
||||
|
||||
// Log backend information
|
||||
// wxLogMessage(wxWebView::GetBackendVersionInfo().ToString());
|
||||
// wxLogMessage("Backend: %s Version: %s",
|
||||
// m_browser->GetClassInfo()->GetClassName(),wxWebView::GetBackendVersionInfo().ToString());
|
||||
// wxLogMessage("User Agent: %s", m_browser->GetUserAgent());
|
||||
|
||||
// Connect the webview events
|
||||
Bind(wxEVT_WEBVIEW_NAVIGATING, &ZUserLogin::OnNavigationRequest, this, m_browser->GetId());
|
||||
Bind(wxEVT_WEBVIEW_NAVIGATED, &ZUserLogin::OnNavigationComplete, this, m_browser->GetId());
|
||||
@@ -113,19 +170,15 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
|
||||
Bind(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, &ZUserLogin::OnFullScreenChanged, this, m_browser->GetId());
|
||||
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ZUserLogin::OnScriptMessage, this, m_browser->GetId());
|
||||
|
||||
// Connect the idle events
|
||||
// Bind(wxEVT_IDLE, &ZUserLogin::OnIdle, this);
|
||||
// Bind(wxEVT_CLOSE_WINDOW, &ZUserLogin::OnClose, this);
|
||||
|
||||
// UI
|
||||
SetTitle(_L("Login"));
|
||||
// Set a more sensible size for web browsing
|
||||
wxSize pSize = FromDIP(wxSize(650, 840));
|
||||
SetSize(pSize);
|
||||
|
||||
int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
|
||||
int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
|
||||
int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
|
||||
int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
|
||||
int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
|
||||
int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
|
||||
wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY);
|
||||
Move(tmpPT);
|
||||
}
|
||||
@@ -221,8 +274,7 @@ void ZUserLogin::OnDocumentLoaded(wxWebViewEvent &evt)
|
||||
{
|
||||
// Only notify if the document is the main frame, not a subframe
|
||||
wxString tmpUrl = evt.GetURL();
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
std::string strHost = agent->get_cloud_service_host();
|
||||
std::string strHost = m_cloud_agent->get_cloud_service_host();
|
||||
|
||||
if (tmpUrl.StartsWith("file://") || tmpUrl.Contains(strHost)) {
|
||||
m_networkOk = true;
|
||||
@@ -270,11 +322,10 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
json j = json::parse(into_u8(str_input));
|
||||
wxString strCmd = j["command"];
|
||||
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (agent && strCmd == "get_login_cmd" && agent->get_cloud_agent()) {
|
||||
if (m_cloud_agent && strCmd == "get_login_cmd") {
|
||||
// Return login config (backend_url, apikey, pkce)
|
||||
// WebView handles provider selection internally
|
||||
std::string login_cmd = agent->build_login_cmd();
|
||||
std::string login_cmd = m_cloud_agent->build_login_cmd();
|
||||
m_loopback_port = 0;
|
||||
try {
|
||||
json cfg = json::parse(login_cmd);
|
||||
@@ -329,13 +380,11 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
|
||||
// Handle message after modal dialog ends to avoid deadlock
|
||||
// Use wxTheApp->CallAfter to ensure it runs after modal loop exits
|
||||
wxTheApp->CallAfter([message_json]() {
|
||||
wxGetApp().handle_script_message(message_json);
|
||||
});
|
||||
const auto provider = m_cloud_agent->get_id();
|
||||
wxTheApp->CallAfter([message_json, provider]() { wxGetApp().handle_script_message(message_json, provider); });
|
||||
}
|
||||
else if (strCmd == "get_localhost_url") {
|
||||
int loopback_port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
|
||||
wxGetApp().start_http_server(loopback_port);
|
||||
int loopback_port = ensure_loopback_port();
|
||||
std::string sequence_id = j["sequence_id"].get<std::string>();
|
||||
CallAfter([this, sequence_id] {
|
||||
json ack_j;
|
||||
@@ -351,8 +400,8 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
else if (strCmd == "thirdparty_login") {
|
||||
if (j["data"].contains("url")) {
|
||||
std::string jump_url = j["data"]["url"].get<std::string>();
|
||||
int loopback_port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
|
||||
wxGetApp().start_http_server(loopback_port);
|
||||
int loopback_port = ensure_loopback_port();
|
||||
jump_url = rewrite_loopback_url(jump_url, loopback_port);
|
||||
CallAfter([this, jump_url] {
|
||||
wxString url = wxString::FromUTF8(jump_url);
|
||||
wxLaunchDefaultBrowser(url);
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
#include <wx/tbarbase.h>
|
||||
#include "wx/textctrl.h"
|
||||
|
||||
#include "slic3r/Utils/ICloudServiceAgent.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class ZUserLogin : public wxDialog
|
||||
{
|
||||
public:
|
||||
ZUserLogin();
|
||||
explicit ZUserLogin(std::shared_ptr<ICloudServiceAgent> cloud_agent);
|
||||
virtual ~ZUserLogin();
|
||||
|
||||
void load_url(wxString &url);
|
||||
@@ -84,6 +86,9 @@ private:
|
||||
|
||||
wxString m_bbl_user_agent;
|
||||
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
int ensure_loopback_port();
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
|
||||
@@ -417,8 +417,13 @@ void WebViewPanel::OnClose(wxCloseEvent& evt)
|
||||
void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event)
|
||||
{
|
||||
auto mainframe = Slic3r::GUI::wxGetApp().mainframe;
|
||||
if (mainframe && mainframe->m_webview == this)
|
||||
Slic3r::GUI::wxGetApp().get_login_info();
|
||||
if (mainframe && mainframe->m_webview == this) {
|
||||
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
|
||||
auto* app_config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (app_config && app_config->has_cloud_provider(BBL_CLOUD_PROVIDER)) {
|
||||
Slic3r::GUI::wxGetApp().get_login_info(BBL_CLOUD_PROVIDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebViewPanel::SetLoginPanelVisibility(bool bshow)
|
||||
@@ -509,6 +514,27 @@ void WebViewPanel::ShowNetpluginTip()
|
||||
RunScript(strJS);
|
||||
}
|
||||
|
||||
void WebViewPanel::SendCloudProvidersInfo()
|
||||
{
|
||||
auto* app_config = wxGetApp().app_config;
|
||||
if (!app_config)
|
||||
return;
|
||||
|
||||
auto providers = app_config->get_cloud_providers();
|
||||
json j;
|
||||
j["command"] = "cloud_providers_info";
|
||||
json data;
|
||||
json provider_array = json::array();
|
||||
for (const auto& p : providers) {
|
||||
provider_array.push_back(p);
|
||||
}
|
||||
data["providers"] = provider_array;
|
||||
j["data"] = data;
|
||||
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", j.dump());
|
||||
RunScript(strJS);
|
||||
}
|
||||
|
||||
void WebViewPanel::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback)
|
||||
{
|
||||
// auto host = wxGetApp().get_http_url(wxGetApp().app_config->get_country_code(), "v1/design-service/design/staffpick");
|
||||
@@ -613,6 +639,7 @@ void WebViewPanel::OnDocumentLoaded(wxWebViewEvent& evt)
|
||||
wxLogMessage("%s", "Document loaded; url='" + evt.GetURL() + "'");
|
||||
}
|
||||
UpdateState();
|
||||
SendCloudProvidersInfo();
|
||||
}
|
||||
|
||||
void WebViewPanel::OnTitleChanged(wxWebViewEvent &evt)
|
||||
|
||||
@@ -99,6 +99,7 @@ public:
|
||||
void OpenModelDetail(std::string id, NetworkAgent *agent);
|
||||
void SendLoginInfo();
|
||||
void ShowNetpluginTip();
|
||||
void SendCloudProvidersInfo();
|
||||
|
||||
void get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback);
|
||||
int get_model_mall_detail_url(std::string *url, std::string id);
|
||||
|
||||
@@ -210,9 +210,19 @@ int ComboBox::Append(const wxString &text,
|
||||
}
|
||||
|
||||
int ComboBox::Append(const wxString &text, const wxBitmap &bitmap, const wxString &group, void *clientData, int style)
|
||||
{
|
||||
return Append(text, bitmap, group, group, clientData, style);
|
||||
}
|
||||
|
||||
int ComboBox::Append(const wxString &text,
|
||||
const wxBitmap &bitmap,
|
||||
const wxString &group_key,
|
||||
const wxString &group_label,
|
||||
void *clientData,
|
||||
int style)
|
||||
{
|
||||
auto valid_bit_map = (&bitmap && bitmap.IsOk()) ? bitmap : wxNullBitmap;
|
||||
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group};
|
||||
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group_key, group_label};
|
||||
item.style = style;
|
||||
items.push_back(item);
|
||||
SetClientDataType(wxClientData_Void);
|
||||
@@ -290,6 +300,18 @@ void ComboBox::SetItemTooltip(unsigned int n, wxString const &value) {
|
||||
if (n == drop.GetSelection()) drop.SetToolTip(value);
|
||||
}
|
||||
|
||||
wxString ComboBox::GetItemAlias(unsigned int n) const
|
||||
{
|
||||
if (n >= items.size()) return wxString();
|
||||
return items[n].alias;
|
||||
}
|
||||
|
||||
void ComboBox::SetItemAlias(unsigned int n, wxString const &value)
|
||||
{
|
||||
if (n >= items.size()) return;
|
||||
items[n].alias = value;
|
||||
}
|
||||
|
||||
wxBitmap ComboBox::GetItemBitmap(unsigned int n) { return items[n].icon; }
|
||||
|
||||
void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap)
|
||||
|
||||
@@ -37,6 +37,12 @@ public:
|
||||
int Append(const wxString &item, const wxBitmap &bitmap = wxNullBitmap, int item_style = 0);
|
||||
int Append(const wxString &item, const wxBitmap &bitmap, void *clientData, int item_style = 0);
|
||||
int Append(const wxString &item, const wxBitmap &bitmap, const wxString &group, void *clientData = nullptr, int item_style = 0);
|
||||
int Append(const wxString& item,
|
||||
const wxBitmap& bitmap,
|
||||
const wxString& group_key,
|
||||
const wxString& group_label,
|
||||
void* clientData = nullptr,
|
||||
int item_style = 0);
|
||||
|
||||
int SetItems(const std::vector<DropDown::Item>& the_items);
|
||||
|
||||
|
||||
@@ -340,14 +340,14 @@ void DropDown::render(wxDC &dc)
|
||||
states2 &= ~StateColor::Enabled;
|
||||
// Skip by group
|
||||
if (group.IsEmpty()) {
|
||||
if (!item.group.IsEmpty()) {
|
||||
if (groups.find(item.group) != groups.end())
|
||||
if (!item.group_key.IsEmpty()) {
|
||||
if (groups.find(item.group_key) != groups.end())
|
||||
continue;
|
||||
groups.insert(item.group);
|
||||
if (!item.group.IsEmpty()) {
|
||||
groups.insert(item.group_key);
|
||||
if (!item.group_key.IsEmpty()) {
|
||||
bool disabled = true;
|
||||
for (int j = i + 1; j < items.size(); ++j) {
|
||||
if (items[i].group != item.group && (items[j].style & DD_ITEM_STYLE_DISABLED) == 0) {
|
||||
if (items[j].group_key == item.group_key && (items[j].style & DD_ITEM_STYLE_DISABLED) == 0) {
|
||||
disabled = false;
|
||||
break;
|
||||
}
|
||||
@@ -357,7 +357,7 @@ void DropDown::render(wxDC &dc)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (item.group != group)
|
||||
if (item.group_key != group)
|
||||
continue;
|
||||
}
|
||||
bool is_hover = index == hover_item;
|
||||
@@ -391,8 +391,8 @@ void DropDown::render(wxDC &dc)
|
||||
pt.y = rcContent.y;
|
||||
}
|
||||
auto text = group.IsEmpty()
|
||||
? (item.group.IsEmpty() ? item.text : item.group)
|
||||
: (item.text.StartsWith(group) && !group.EndsWith(' ') ? item.text.substr(group.size()).Trim(false) : item.text);
|
||||
? (item.group_key.IsEmpty() ? item.text : item.group_label)
|
||||
: item.text;
|
||||
if (!text_off && !text.IsEmpty()) {
|
||||
wxSize tSize = dc.GetMultiLineTextExtent(text);
|
||||
if (pt.x + tSize.x > rcContent.GetRight()) {
|
||||
@@ -405,7 +405,7 @@ void DropDown::render(wxDC &dc)
|
||||
dc.SetFont(GetFont());
|
||||
dc.SetTextForeground(text_color.colorForStates(states2));
|
||||
dc.DrawText(text, pt);
|
||||
if (group.IsEmpty() && !item.group.IsEmpty()) {
|
||||
if (group.IsEmpty() && !item.group_key.IsEmpty()) {
|
||||
auto szBmp = arrow_bitmap.GetBmpSize();
|
||||
pt.x = rcContent.GetRight() - szBmp.x - 5;
|
||||
pt.y = rcContent.y + (rcContent.height - szBmp.y) / 2;
|
||||
@@ -430,18 +430,18 @@ int DropDown::hoverIndex()
|
||||
auto &item = items[i];
|
||||
// Skip by group
|
||||
if (group.IsEmpty()) {
|
||||
if (!item.group.IsEmpty()) {
|
||||
if (groups.find(item.group) == groups.end())
|
||||
groups.insert(item.group);
|
||||
if (!item.group_key.IsEmpty()) {
|
||||
if (groups.find(item.group_key) == groups.end())
|
||||
groups.insert(item.group_key);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (item.group != group)
|
||||
if (item.group_key != group)
|
||||
continue;
|
||||
}
|
||||
if (++index == hover_item)
|
||||
return (item.group.IsEmpty() || !group.IsEmpty()) ? i : -i - 2;
|
||||
return (item.group_key.IsEmpty() || !group.IsEmpty()) ? i : -i - 2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -455,7 +455,7 @@ int DropDown::selectedItem()
|
||||
if (count == items.size() && subDropDown == nullptr)
|
||||
return selection;
|
||||
auto & sel = items[selection];
|
||||
if (group.IsEmpty() ? !sel.group.IsEmpty() : sel.group != group)
|
||||
if (group.IsEmpty() ? !sel.group_key.IsEmpty() : sel.group_key != group)
|
||||
return -1;
|
||||
if (selection == 0)
|
||||
return 0;
|
||||
@@ -465,14 +465,14 @@ int DropDown::selectedItem()
|
||||
auto &item = items[i];
|
||||
// Skip by group
|
||||
if (group.IsEmpty()) {
|
||||
if (!item.group.IsEmpty()) {
|
||||
if (groups.find(item.group) == groups.end())
|
||||
groups.insert(item.group);
|
||||
if (!item.group_key.IsEmpty()) {
|
||||
if (groups.find(item.group_key) == groups.end())
|
||||
groups.insert(item.group_key);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (item.group != group)
|
||||
if (item.group_key != group)
|
||||
continue;
|
||||
}
|
||||
++index;
|
||||
@@ -493,24 +493,24 @@ void DropDown::messureSize()
|
||||
auto &item = items[i];
|
||||
// Skip by group
|
||||
if (group.IsEmpty()) {
|
||||
if (!item.group.IsEmpty()) {
|
||||
if (groups.find(item.group) == groups.end())
|
||||
groups.insert(item.group);
|
||||
if (!item.group_key.IsEmpty()) {
|
||||
if (groups.find(item.group_key) == groups.end())
|
||||
groups.insert(item.group_key);
|
||||
else
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (item.group != group)
|
||||
if (item.group_key != group)
|
||||
continue;
|
||||
}
|
||||
++count;
|
||||
wxSize size1;
|
||||
if (!text_off) {
|
||||
auto text = group.IsEmpty()
|
||||
? (item.group.IsEmpty() ? item.text : item.group)
|
||||
: (item.text.StartsWith(group) && !group.EndsWith(' ') ? item.text.substr(group.size()).Trim(false) : item.text);
|
||||
? (item.group_key.IsEmpty() ? item.text : item.group_label)
|
||||
: item.text;
|
||||
size1 = dc.GetMultiLineTextExtent(text);
|
||||
if (group.IsEmpty() && !item.group.IsEmpty())
|
||||
if (group.IsEmpty() && !item.group_key.IsEmpty())
|
||||
size1.x += 5 + arrow_bitmap.GetBmpWidth();
|
||||
}
|
||||
if (item.icon.IsOk()) {
|
||||
@@ -696,7 +696,7 @@ void DropDown::mouseMove(wxMouseEvent &event)
|
||||
int index = hoverIndex();
|
||||
if (index < -1) {
|
||||
auto & drop = *subDropDown;
|
||||
drop.group = items[-index - 2].group;
|
||||
drop.group = items[-index - 2].group_key;
|
||||
drop.need_sync = true;
|
||||
drop.messureSize();
|
||||
drop.autoPosition();
|
||||
|
||||
@@ -26,7 +26,8 @@ public:
|
||||
wxBitmap icon;
|
||||
wxBitmap icon_textctrl;// display icon for TextInput.eg.PrinterInfoBox
|
||||
void * data{nullptr};
|
||||
wxString group{};
|
||||
wxString group_key{};
|
||||
wxString group_label{};
|
||||
wxString alias{};
|
||||
wxString tip{};
|
||||
int flag{0};
|
||||
|
||||
@@ -2,9 +2,85 @@
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "Http.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
std::string convert_studio_language_to_api(std::string lang_code)
|
||||
{
|
||||
boost::replace_all(lang_code, "_", "-");
|
||||
return lang_code;
|
||||
}
|
||||
|
||||
std::string normalize_homepage_auth_command(std::string payload)
|
||||
{
|
||||
if (payload.empty()) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
try {
|
||||
auto json = nlohmann::json::parse(payload);
|
||||
if (json.contains("command") && json["command"].is_string()) {
|
||||
std::string command = json["command"].get<std::string>();
|
||||
if (command == "studio_userlogin") {
|
||||
json["command"] = "studio_bambu_userlogin";
|
||||
} else if (command == "studio_useroffline") {
|
||||
json["command"] = "studio_bambu_useroffline";
|
||||
}
|
||||
return json.dump();
|
||||
}
|
||||
} catch (...) {
|
||||
boost::replace_first(payload, "\"command\":\"studio_userlogin\"", "\"command\":\"studio_bambu_userlogin\"");
|
||||
boost::replace_first(payload, "\"command\":\"studio_useroffline\"", "\"command\":\"studio_bambu_useroffline\"");
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::map<std::string, std::string> BBLCloudServiceAgent::get_extra_header()
|
||||
{
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
extra_headers.emplace("X-BBL-Client-Type", "slicer");
|
||||
extra_headers.emplace("X-BBL-Client-Name", SLIC3R_APP_NAME);
|
||||
extra_headers.emplace("X-BBL-Client-Version", GUI::wxGetApp().get_bbl_client_version());
|
||||
#if defined(__WINDOWS__)
|
||||
#ifdef _M_X64
|
||||
extra_headers.emplace("X-BBL-OS-Type", "windows");
|
||||
#else
|
||||
extra_headers.emplace("X-BBL-OS-Type", "windows_arm");
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
extra_headers.emplace("X-BBL-OS-Type", "macos");
|
||||
#elif defined(__LINUX__)
|
||||
extra_headers.emplace("X-BBL-OS-Type", "linux");
|
||||
#endif
|
||||
|
||||
int major = 0, minor = 0, micro = 0;
|
||||
wxGetOsVersion(&major, &minor, µ);
|
||||
|
||||
std::ostringstream os_version;
|
||||
os_version << major << "." << minor << "." << micro;
|
||||
extra_headers.emplace("X-BBL-OS-Version", os_version.str());
|
||||
|
||||
auto& app = GUI::wxGetApp();
|
||||
if (app.app_config) {
|
||||
extra_headers.emplace("X-BBL-Device-ID", app.app_config->get("slicer_uuid"));
|
||||
}
|
||||
|
||||
extra_headers.emplace("X-BBL-Language",
|
||||
convert_studio_language_to_api(app.current_language_code_safe().ToStdString()));
|
||||
return extra_headers;
|
||||
}
|
||||
|
||||
BBLCloudServiceAgent::BBLCloudServiceAgent() = default;
|
||||
|
||||
BBLCloudServiceAgent::~BBLCloudServiceAgent() = default;
|
||||
@@ -59,6 +135,8 @@ int BBLCloudServiceAgent::set_country_code(std::string country_code)
|
||||
|
||||
int BBLCloudServiceAgent::start()
|
||||
{
|
||||
set_extra_http_header();
|
||||
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start();
|
||||
@@ -159,7 +237,7 @@ std::string BBLCloudServiceAgent::build_login_cmd()
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_build_login_cmd();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
return normalize_homepage_auth_command(func(agent));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -170,7 +248,7 @@ std::string BBLCloudServiceAgent::build_logout_cmd()
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_build_logout_cmd();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
return normalize_homepage_auth_command(func(agent));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -181,7 +259,7 @@ std::string BBLCloudServiceAgent::build_login_info()
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_build_login_info();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
return normalize_homepage_auth_command(func(agent));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -211,21 +289,6 @@ bool BBLCloudServiceAgent::ensure_token_fresh(const std::string& reason)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Auth Callbacks (merged from BBLAuthAgent)
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::set_on_user_login_fn(OnUserLoginFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_user_login_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Server Connectivity
|
||||
// ============================================================================
|
||||
@@ -767,28 +830,22 @@ int BBLCloudServiceAgent::get_model_mall_rating_result(int job_id, std::string&
|
||||
// Extra Features
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::set_extra_http_header(std::map<std::string, std::string> extra_headers)
|
||||
int BBLCloudServiceAgent::set_extra_http_header()
|
||||
{
|
||||
// Orca: not sure if this required to login into bbl cloud
|
||||
// Slic3r::Http::set_extra_headers(extra_headers);
|
||||
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto agent = plugin.get_agent();
|
||||
|
||||
auto func = plugin.get_set_extra_http_header();
|
||||
if (func && agent) {
|
||||
auto extra_headers = get_extra_header();
|
||||
return func(agent, extra_headers);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_studio_info_url()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_studio_info_url();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_mw_user_preference(std::function<void(std::string)> callback)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
@@ -825,24 +882,36 @@ std::string BBLCloudServiceAgent::get_version()
|
||||
// Cloud Callbacks
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::set_on_server_connected_fn(OnServerConnectedFn fn)
|
||||
int BBLCloudServiceAgent::set_on_server_connected_fn(AppOnServerConnectedFn fn)
|
||||
{
|
||||
m_app_on_server_connected_fn = fn;
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_server_connected_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
// Register raw callback with DLL, wrap to inject CloudEvent
|
||||
return func(agent, [this](int return_code, int reason_code) {
|
||||
if (m_app_on_server_connected_fn) {
|
||||
m_app_on_server_connected_fn(CloudEvent{BBL_CLOUD_PROVIDER}, return_code, reason_code);
|
||||
}
|
||||
});
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_on_http_error_fn(OnHttpErrorFn fn)
|
||||
int BBLCloudServiceAgent::set_on_http_error_fn(AppOnHttpErrorFn fn)
|
||||
{
|
||||
m_app_on_http_error_fn = fn;
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_http_error_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
// Register raw callback with DLL, wrap to inject CloudEvent
|
||||
return func(agent, [this](unsigned http_code, std::string http_body) {
|
||||
if (m_app_on_http_error_fn) {
|
||||
m_app_on_http_error_fn(CloudEvent{BBL_CLOUD_PROVIDER}, http_code, http_body);
|
||||
}
|
||||
});
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
BBLCloudServiceAgent();
|
||||
~BBLCloudServiceAgent() override;
|
||||
|
||||
std::string get_id() const override { return BBL_CLOUD_PROVIDER; }
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Auth Methods
|
||||
// ========================================================================
|
||||
@@ -49,9 +51,6 @@ public:
|
||||
std::string get_refresh_token() const override;
|
||||
bool ensure_token_fresh(const std::string& reason) override;
|
||||
|
||||
// Auth Callbacks
|
||||
int set_on_user_login_fn(OnUserLoginFn fn) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Cloud Methods
|
||||
// ========================================================================
|
||||
@@ -116,20 +115,22 @@ public:
|
||||
int get_model_mall_rating_result(int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error) override;
|
||||
|
||||
// Extra Features
|
||||
int set_extra_http_header(std::map<std::string, std::string> extra_headers) override;
|
||||
std::string get_studio_info_url() override;
|
||||
int get_mw_user_preference(std::function<void(std::string)> callback) override;
|
||||
int get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback) override;
|
||||
std::string get_version() override;
|
||||
|
||||
// Cloud Callbacks
|
||||
int set_on_server_connected_fn(OnServerConnectedFn fn) override;
|
||||
int set_on_http_error_fn(OnHttpErrorFn fn) override;
|
||||
int set_on_server_connected_fn(AppOnServerConnectedFn fn) override;
|
||||
int set_on_http_error_fn(AppOnHttpErrorFn fn) override;
|
||||
int set_get_country_code_fn(GetCountryCodeFn fn) override;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
|
||||
private:
|
||||
int set_extra_http_header();
|
||||
static std::map<std::string, std::string> get_extra_header();
|
||||
bool m_enable_track{false};
|
||||
AppOnServerConnectedFn m_app_on_server_connected_fn;
|
||||
AppOnHttpErrorFn m_app_on_http_error_fn;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -546,7 +546,6 @@ void BBLNetworkPlugin::load_all_function_pointers()
|
||||
m_set_country_code = reinterpret_cast<func_set_country_code>(get_function("bambu_network_set_country_code"));
|
||||
m_start = reinterpret_cast<func_start>(get_function("bambu_network_start"));
|
||||
m_set_on_ssdp_msg_fn = reinterpret_cast<func_set_on_ssdp_msg_fn>(get_function("bambu_network_set_on_ssdp_msg_fn"));
|
||||
m_set_on_user_login_fn = reinterpret_cast<func_set_on_user_login_fn>(get_function("bambu_network_set_on_user_login_fn"));
|
||||
m_set_on_printer_connected_fn = reinterpret_cast<func_set_on_printer_connected_fn>(get_function("bambu_network_set_on_printer_connected_fn"));
|
||||
m_set_on_server_connected_fn = reinterpret_cast<func_set_on_server_connected_fn>(get_function("bambu_network_set_on_server_connected_fn"));
|
||||
m_set_on_http_error_fn = reinterpret_cast<func_set_on_http_error_fn>(get_function("bambu_network_set_on_http_error_fn"));
|
||||
@@ -601,7 +600,6 @@ void BBLNetworkPlugin::load_all_function_pointers()
|
||||
m_get_setting_list = reinterpret_cast<func_get_setting_list>(get_function("bambu_network_get_setting_list"));
|
||||
m_get_setting_list2 = reinterpret_cast<func_get_setting_list2>(get_function("bambu_network_get_setting_list2"));
|
||||
m_delete_setting = reinterpret_cast<func_delete_setting>(get_function("bambu_network_delete_setting"));
|
||||
m_get_studio_info_url = reinterpret_cast<func_get_studio_info_url>(get_function("bambu_network_get_studio_info_url"));
|
||||
m_set_extra_http_header = reinterpret_cast<func_set_extra_http_header>(get_function("bambu_network_set_extra_http_header"));
|
||||
m_get_my_message = reinterpret_cast<func_get_my_message>(get_function("bambu_network_get_my_message"));
|
||||
m_check_user_task_report = reinterpret_cast<func_check_user_task_report>(get_function("bambu_network_check_user_task_report"));
|
||||
@@ -650,7 +648,6 @@ void BBLNetworkPlugin::clear_all_function_pointers()
|
||||
m_set_country_code = nullptr;
|
||||
m_start = nullptr;
|
||||
m_set_on_ssdp_msg_fn = nullptr;
|
||||
m_set_on_user_login_fn = nullptr;
|
||||
m_set_on_printer_connected_fn = nullptr;
|
||||
m_set_on_server_connected_fn = nullptr;
|
||||
m_set_on_http_error_fn = nullptr;
|
||||
@@ -705,7 +702,6 @@ void BBLNetworkPlugin::clear_all_function_pointers()
|
||||
m_get_setting_list = nullptr;
|
||||
m_get_setting_list2 = nullptr;
|
||||
m_delete_setting = nullptr;
|
||||
m_get_studio_info_url = nullptr;
|
||||
m_set_extra_http_header = nullptr;
|
||||
m_get_my_message = nullptr;
|
||||
m_check_user_task_report = nullptr;
|
||||
|
||||
@@ -30,7 +30,6 @@ typedef int (*func_set_cert_file)(void *agent, std::string folder, std::string f
|
||||
typedef int (*func_set_country_code)(void *agent, std::string country_code);
|
||||
typedef int (*func_start)(void *agent);
|
||||
typedef int (*func_set_on_ssdp_msg_fn)(void *agent, OnMsgArrivedFn fn);
|
||||
typedef int (*func_set_on_user_login_fn)(void *agent, OnUserLoginFn fn);
|
||||
typedef int (*func_set_on_printer_connected_fn)(void *agent, OnPrinterConnectedFn fn);
|
||||
typedef int (*func_set_on_server_connected_fn)(void *agent, OnServerConnectedFn fn);
|
||||
typedef int (*func_set_on_http_error_fn)(void *agent, OnHttpErrorFn fn);
|
||||
@@ -85,7 +84,6 @@ typedef int (*func_put_setting)(void *agent, std::string setting_id, std::string
|
||||
typedef int (*func_get_setting_list)(void *agent, std::string bundle_version, ProgressFn pro_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_get_setting_list2)(void *agent, std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_delete_setting)(void *agent, std::string setting_id);
|
||||
typedef std::string (*func_get_studio_info_url)(void *agent);
|
||||
typedef int (*func_set_extra_http_header)(void *agent, std::map<std::string, std::string> extra_headers);
|
||||
typedef int (*func_get_my_message)(void *agent, int type, int after, int limit, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_check_user_task_report)(void *agent, int* task_id, bool* printable);
|
||||
@@ -285,7 +283,6 @@ public:
|
||||
func_set_country_code get_set_country_code() const { return m_set_country_code; }
|
||||
func_start get_start() const { return m_start; }
|
||||
func_set_on_ssdp_msg_fn get_set_on_ssdp_msg_fn() const { return m_set_on_ssdp_msg_fn; }
|
||||
func_set_on_user_login_fn get_set_on_user_login_fn() const { return m_set_on_user_login_fn; }
|
||||
func_set_on_printer_connected_fn get_set_on_printer_connected_fn() const { return m_set_on_printer_connected_fn; }
|
||||
func_set_on_server_connected_fn get_set_on_server_connected_fn() const { return m_set_on_server_connected_fn; }
|
||||
func_set_on_http_error_fn get_set_on_http_error_fn() const { return m_set_on_http_error_fn; }
|
||||
@@ -340,7 +337,6 @@ public:
|
||||
func_get_setting_list get_get_setting_list() const { return m_get_setting_list; }
|
||||
func_get_setting_list2 get_get_setting_list2() const { return m_get_setting_list2; }
|
||||
func_delete_setting get_delete_setting() const { return m_delete_setting; }
|
||||
func_get_studio_info_url get_get_studio_info_url() const { return m_get_studio_info_url; }
|
||||
func_set_extra_http_header get_set_extra_http_header() const { return m_set_extra_http_header; }
|
||||
func_get_my_message get_get_my_message() const { return m_get_my_message; }
|
||||
func_check_user_task_report get_check_user_task_report() const { return m_check_user_task_report; }
|
||||
@@ -421,7 +417,6 @@ private:
|
||||
func_set_country_code m_set_country_code{nullptr};
|
||||
func_start m_start{nullptr};
|
||||
func_set_on_ssdp_msg_fn m_set_on_ssdp_msg_fn{nullptr};
|
||||
func_set_on_user_login_fn m_set_on_user_login_fn{nullptr};
|
||||
func_set_on_printer_connected_fn m_set_on_printer_connected_fn{nullptr};
|
||||
func_set_on_server_connected_fn m_set_on_server_connected_fn{nullptr};
|
||||
func_set_on_http_error_fn m_set_on_http_error_fn{nullptr};
|
||||
@@ -476,7 +471,6 @@ private:
|
||||
func_get_setting_list m_get_setting_list{nullptr};
|
||||
func_get_setting_list2 m_get_setting_list2{nullptr};
|
||||
func_delete_setting m_delete_setting{nullptr};
|
||||
func_get_studio_info_url m_get_studio_info_url{nullptr};
|
||||
func_set_extra_http_header m_set_extra_http_header{nullptr};
|
||||
func_get_my_message m_get_my_message{nullptr};
|
||||
func_check_user_task_report m_check_user_task_report{nullptr};
|
||||
|
||||
@@ -208,6 +208,54 @@ int BBLPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Subscriptions
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::start_subscribe(std::string module)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_subscribe();
|
||||
if (func && agent) {
|
||||
return func(agent, module);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::stop_subscribe(std::string module)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_stop_subscribe();
|
||||
if (func && agent) {
|
||||
return func(agent, module);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::add_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_add_subscribe();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_list);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::del_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_del_subscribe();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_list);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Information
|
||||
// ============================================================================
|
||||
|
||||
@@ -51,6 +51,12 @@ public:
|
||||
std::string get_user_selected_machine() override;
|
||||
int set_user_selected_machine(std::string dev_id) override;
|
||||
|
||||
// Subscriptions
|
||||
int start_subscribe(std::string module) override;
|
||||
int stop_subscribe(std::string module) override;
|
||||
int add_subscribe(std::vector<std::string> dev_list) override;
|
||||
int del_subscribe(std::vector<std::string> dev_list) override;
|
||||
|
||||
/**
|
||||
* Get agent information.
|
||||
*
|
||||
|
||||
@@ -1010,7 +1010,7 @@ bool CalibUtils::calib_generic_auto_pa_cali(const std::vector<CalibInfo> &calib_
|
||||
}
|
||||
js["filament_id"] = filament_ids;
|
||||
js["printer_type"] = obj_->printer_type;
|
||||
NetworkAgent *agent = GUI::wxGetApp().getAgent();
|
||||
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
||||
if (agent)
|
||||
agent->track_event("cali", js.dump());
|
||||
} catch (...) {}
|
||||
|
||||
@@ -608,6 +608,16 @@ Http& Http::ca_file(const std::string &name)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::tls_verify(bool enable)
|
||||
{
|
||||
if (p) {
|
||||
::curl_easy_setopt(p->curl, CURLOPT_SSL_VERIFYPEER, enable ? 1L : 0L);
|
||||
::curl_easy_setopt(p->curl, CURLOPT_SSL_VERIFYHOST, enable ? 2L : 0L);
|
||||
::curl_easy_setopt(p->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::form_clear() {
|
||||
if (p) {
|
||||
if (p->form) {
|
||||
|
||||
@@ -119,6 +119,10 @@ public:
|
||||
// specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store.
|
||||
// See also ca_file_supported().
|
||||
Http& ca_file(const std::string &filename);
|
||||
// Enables TLS certificate verification (peer + hostname). Off by default in the
|
||||
// shared client for compatibility with self-signed print hosts; opt in for any
|
||||
// request carrying authentication credentials or sensitive user data.
|
||||
Http& tls_verify(bool enable);
|
||||
|
||||
Http& form_clear();
|
||||
// Add a HTTP multipart form field
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "bambu_networking.hpp"
|
||||
#include "../../libslic3r/ProjectTask.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
@@ -35,6 +36,17 @@ namespace Slic3r {
|
||||
* access tokens for cloud-relay operations without coupling to a specific auth
|
||||
* implementation.
|
||||
*/
|
||||
|
||||
static const std::string ORCA_CLOUD_PROVIDER("orca");
|
||||
static const std::string BBL_CLOUD_PROVIDER("bbl");
|
||||
|
||||
struct CloudEvent {
|
||||
std::string provider; // ORCA_CLOUD_PROVIDER or BBL_CLOUD_PROVIDER
|
||||
};
|
||||
|
||||
using AppOnServerConnectedFn = std::function<void(CloudEvent event, int return_code, int reason_code)>;
|
||||
using AppOnHttpErrorFn = std::function<void(CloudEvent event, unsigned http_code, std::string http_body)>;
|
||||
|
||||
class ICloudServiceAgent {
|
||||
public:
|
||||
virtual ~ICloudServiceAgent() = default;
|
||||
@@ -65,6 +77,7 @@ public:
|
||||
*/
|
||||
virtual int set_country_code(std::string country_code) = 0;
|
||||
|
||||
virtual std::string get_id() const = 0;
|
||||
/**
|
||||
* Start the agent, performing any expensive initialization.
|
||||
* Typically regenerates PKCE bundles and attempts silent sign-in.
|
||||
@@ -82,7 +95,6 @@ public:
|
||||
* 2. WebView/OAuth: {"command": "user_login", "data": {...}}
|
||||
* 3. Token format: {"data": {"token": "...", "refresh_token": "...", "user": {...}}}
|
||||
*
|
||||
* On completion, invokes the registered OnUserLoginFn callback.
|
||||
*/
|
||||
virtual int change_user(std::string user_info) = 0;
|
||||
|
||||
@@ -425,16 +437,6 @@ public:
|
||||
// ========================================================================
|
||||
// Extra Features
|
||||
// ========================================================================
|
||||
/**
|
||||
* Set additional HTTP headers for all requests.
|
||||
*/
|
||||
virtual int set_extra_http_header(std::map<std::string, std::string> extra_headers) = 0;
|
||||
|
||||
/**
|
||||
* Get the studio info URL.
|
||||
*/
|
||||
virtual std::string get_studio_info_url() = 0;
|
||||
|
||||
/**
|
||||
* Fetch MakerWorld user preferences.
|
||||
*/
|
||||
@@ -453,21 +455,15 @@ public:
|
||||
// ========================================================================
|
||||
// Callback Registration
|
||||
// ========================================================================
|
||||
/**
|
||||
* Register the login status callback.
|
||||
* Called after change_user() finishes or when the session expires.
|
||||
*/
|
||||
virtual int set_on_user_login_fn(OnUserLoginFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register server connection status callback.
|
||||
*/
|
||||
virtual int set_on_server_connected_fn(OnServerConnectedFn fn) = 0;
|
||||
virtual int set_on_server_connected_fn(AppOnServerConnectedFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register HTTP error callback.
|
||||
*/
|
||||
virtual int set_on_http_error_fn(OnHttpErrorFn fn) = 0;
|
||||
virtual int set_on_http_error_fn(AppOnHttpErrorFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Provide country code getter callback.
|
||||
|
||||
@@ -59,7 +59,6 @@ class IPrinterAgent {
|
||||
public:
|
||||
virtual ~IPrinterAgent() = default;
|
||||
|
||||
// ========================================================================
|
||||
// Cloud Agent Dependency
|
||||
// ========================================================================
|
||||
/**
|
||||
@@ -158,6 +157,29 @@ public:
|
||||
*/
|
||||
virtual int set_user_selected_machine(std::string dev_id) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Subscriptions
|
||||
// ========================================================================
|
||||
/**
|
||||
* Subscribe to a logical module (for example app- or tunnel-scoped streams).
|
||||
*/
|
||||
virtual int start_subscribe(std::string module) { (void) module; return BAMBU_NETWORK_SUCCESS; }
|
||||
|
||||
/**
|
||||
* Stop listening to a formerly subscribed module.
|
||||
*/
|
||||
virtual int stop_subscribe(std::string module) { (void) module; return BAMBU_NETWORK_SUCCESS; }
|
||||
|
||||
/**
|
||||
* Subscribe to push streams for specific device identifiers.
|
||||
*/
|
||||
virtual int add_subscribe(std::vector<std::string> dev_list) { (void) dev_list; return BAMBU_NETWORK_SUCCESS; }
|
||||
|
||||
/**
|
||||
* Remove device-level subscriptions.
|
||||
*/
|
||||
virtual int del_subscribe(std::vector<std::string> dev_list) { (void) dev_list; return BAMBU_NETWORK_SUCCESS; }
|
||||
|
||||
// ========================================================================
|
||||
// Print Job Operations
|
||||
// ========================================================================
|
||||
|
||||
+695
-626
@@ -7,11 +7,31 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
#include "BBLCloudServiceAgent.hpp"
|
||||
#include "BBLPrinterAgent.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename Fn>
|
||||
int invoke_on_all_cloud_agents(const std::map<std::string, std::shared_ptr<ICloudServiceAgent>>& cloud_agents, Fn&& fn)
|
||||
{
|
||||
if (cloud_agents.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
for (const auto& cloud_agent_pair : cloud_agents) {
|
||||
const int ret = fn(*cloud_agent_pair.second);
|
||||
if (result == 0 && ret != 0) {
|
||||
result = ret;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool NetworkAgent::use_legacy_network = true;
|
||||
|
||||
// ============================================================================
|
||||
@@ -28,72 +48,36 @@ std::string NetworkAgent::get_versioned_library_path(const std::string& version)
|
||||
return BBLNetworkPlugin::get_versioned_library_path(version);
|
||||
}
|
||||
|
||||
bool NetworkAgent::versioned_library_exists(const std::string& version)
|
||||
{
|
||||
return BBLNetworkPlugin::versioned_library_exists(version);
|
||||
}
|
||||
bool NetworkAgent::versioned_library_exists(const std::string& version) { return BBLNetworkPlugin::versioned_library_exists(version); }
|
||||
|
||||
bool NetworkAgent::legacy_library_exists()
|
||||
{
|
||||
return BBLNetworkPlugin::legacy_library_exists();
|
||||
}
|
||||
bool NetworkAgent::legacy_library_exists() { return BBLNetworkPlugin::legacy_library_exists(); }
|
||||
|
||||
void NetworkAgent::remove_legacy_library()
|
||||
{
|
||||
BBLNetworkPlugin::remove_legacy_library();
|
||||
}
|
||||
void NetworkAgent::remove_legacy_library() { BBLNetworkPlugin::remove_legacy_library(); }
|
||||
|
||||
std::vector<std::string> NetworkAgent::scan_plugin_versions()
|
||||
{
|
||||
return BBLNetworkPlugin::scan_plugin_versions();
|
||||
}
|
||||
std::vector<std::string> NetworkAgent::scan_plugin_versions() { return BBLNetworkPlugin::scan_plugin_versions(); }
|
||||
|
||||
int NetworkAgent::initialize_network_module(bool using_backup, const std::string& version)
|
||||
{
|
||||
return BBLNetworkPlugin::instance().initialize(using_backup, version);
|
||||
}
|
||||
|
||||
int NetworkAgent::unload_network_module()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().unload();
|
||||
}
|
||||
int NetworkAgent::unload_network_module() { return BBLNetworkPlugin::instance().unload(); }
|
||||
|
||||
bool NetworkAgent::is_network_module_loaded()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().is_loaded();
|
||||
}
|
||||
bool NetworkAgent::is_network_module_loaded() { return BBLNetworkPlugin::instance().is_loaded(); }
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
HMODULE NetworkAgent::get_bambu_source_entry()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().get_bambu_source_entry();
|
||||
}
|
||||
HMODULE NetworkAgent::get_bambu_source_entry() { return BBLNetworkPlugin::instance().get_bambu_source_entry(); }
|
||||
#else
|
||||
void* NetworkAgent::get_bambu_source_entry()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().get_bambu_source_entry();
|
||||
}
|
||||
void* NetworkAgent::get_bambu_source_entry() { return BBLNetworkPlugin::instance().get_bambu_source_entry(); }
|
||||
#endif
|
||||
|
||||
std::string NetworkAgent::get_version()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().get_version();
|
||||
}
|
||||
std::string NetworkAgent::get_version() { return BBLNetworkPlugin::instance().get_version(); }
|
||||
|
||||
void* NetworkAgent::get_network_function(const char* name)
|
||||
{
|
||||
return BBLNetworkPlugin::instance().get_network_function(name);
|
||||
}
|
||||
void* NetworkAgent::get_network_function(const char* name) { return BBLNetworkPlugin::instance().get_network_function(name); }
|
||||
|
||||
NetworkLibraryLoadError NetworkAgent::get_load_error()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().get_load_error();
|
||||
}
|
||||
NetworkLibraryLoadError NetworkAgent::get_load_error() { return BBLNetworkPlugin::instance().get_load_error(); }
|
||||
|
||||
void NetworkAgent::clear_load_error()
|
||||
{
|
||||
BBLNetworkPlugin::instance().clear_load_error();
|
||||
}
|
||||
void NetworkAgent::clear_load_error() { BBLNetworkPlugin::instance().clear_load_error(); }
|
||||
|
||||
void NetworkAgent::set_load_error(const std::string& message, const std::string& technical_details, const std::string& attempted_path)
|
||||
{
|
||||
@@ -104,28 +88,18 @@ void NetworkAgent::set_load_error(const std::string& message, const std::string&
|
||||
// Constructors
|
||||
// ============================================================================
|
||||
|
||||
NetworkAgent::NetworkAgent(std::string log_dir)
|
||||
NetworkAgent::NetworkAgent(std::shared_ptr<ICloudServiceAgent> cloud_agent, std::shared_ptr<IPrinterAgent> printer_agent)
|
||||
: m_printer_agent(std::move(printer_agent))
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
|
||||
if (plugin.is_loaded()) {
|
||||
// Create agent if not already created
|
||||
if (!plugin.has_agent()) {
|
||||
plugin.create_agent(log_dir);
|
||||
}
|
||||
|
||||
m_cloud_agent = std::make_shared<BBLCloudServiceAgent>();
|
||||
m_printer_agent = std::make_shared<BBLPrinterAgent>();
|
||||
m_printer_agent->set_cloud_agent(m_cloud_agent);
|
||||
m_printer_agent_id = m_printer_agent->get_agent_info().id;
|
||||
if (!cloud_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Null cloud agent provided, skipping agent initialization";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkAgent::NetworkAgent(std::shared_ptr<ICloudServiceAgent> cloud_agent,
|
||||
std::shared_ptr<IPrinterAgent> printer_agent)
|
||||
: m_cloud_agent(std::move(cloud_agent))
|
||||
, m_printer_agent(std::move(printer_agent))
|
||||
{
|
||||
if (cloud_agent->get_id().empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Invalid cloud agent with empty ID provided, skipping agent initialization";
|
||||
return;
|
||||
}
|
||||
m_cloud_agents.emplace(cloud_agent->get_id(), std::move(cloud_agent));
|
||||
}
|
||||
|
||||
NetworkAgent::~NetworkAgent()
|
||||
@@ -134,57 +108,37 @@ NetworkAgent::~NetworkAgent()
|
||||
// The singleton manages the agent lifecycle
|
||||
}
|
||||
|
||||
void NetworkAgent::add_cloud_agent(const std::string& provider, std::shared_ptr<ICloudServiceAgent> agent)
|
||||
{
|
||||
if (agent) {
|
||||
m_cloud_agents[provider] = std::move(agent);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent)
|
||||
{
|
||||
// Local copies to allow safe access after releasing the lock.
|
||||
// This pattern ensures the objects stay alive (via shared_ptr refcount) even if
|
||||
// another thread modifies m_printer_agent or m_printer_callbacks after we unlock.
|
||||
std::shared_ptr<IPrinterAgent> old_printer_agent;
|
||||
std::shared_ptr<IPrinterAgent> new_printer_agent;
|
||||
PrinterCallbacks callbacks;
|
||||
|
||||
{
|
||||
// Critical section: protect access to shared state
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
|
||||
if (!printer_agent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disconnect all callbacks from the old agent
|
||||
apply_printer_callbacks(m_printer_agent, callbacks);
|
||||
// Capture the old agent before overwriting so we can disconnect it outside the lock
|
||||
old_printer_agent = m_printer_agent;
|
||||
// Take ownership of the incoming agent and update the agent ID
|
||||
m_printer_agent = std::move(printer_agent);
|
||||
m_printer_agent_id = m_printer_agent->get_agent_info().id;
|
||||
|
||||
// Create local shared_ptr copies - this increments the reference count,
|
||||
// guaranteeing the agent object stays alive even if m_printer_agent
|
||||
// is modified by another thread after we unlock
|
||||
new_printer_agent = m_printer_agent;
|
||||
callbacks = m_printer_callbacks;
|
||||
if (!printer_agent) {
|
||||
return;
|
||||
}
|
||||
// Lock released here - m_agent_mutex is now free for other threads
|
||||
|
||||
// Disconnect the old agent's connections/threads. The cache keeps it alive,
|
||||
// but we release its network resources while it's not the active agent.
|
||||
if (old_printer_agent && old_printer_agent != new_printer_agent)
|
||||
// Disconnect all callbacks from the old agent
|
||||
auto old_printer_agent = m_printer_agent;
|
||||
|
||||
m_printer_agent = std::move(printer_agent);
|
||||
m_printer_agent_id = m_printer_agent->get_agent_info().id;
|
||||
|
||||
// Disconnect the old agent's connections/threads.
|
||||
if (old_printer_agent && old_printer_agent != m_printer_agent) {
|
||||
old_printer_agent->disconnect_printer();
|
||||
apply_printer_callbacks(old_printer_agent, {});
|
||||
}
|
||||
|
||||
// Apply callbacks OUTSIDE the lock to avoid deadlock risk and minimize
|
||||
// critical section duration. The local shared_ptr copy ensures the agent
|
||||
// cannot be destroyed while we're using it.
|
||||
apply_printer_callbacks(new_printer_agent, callbacks);
|
||||
apply_printer_callbacks(m_printer_agent, m_printer_callbacks);
|
||||
}
|
||||
|
||||
void* NetworkAgent::get_network_agent()
|
||||
{
|
||||
return BBLNetworkPlugin::instance().get_agent();
|
||||
}
|
||||
void* NetworkAgent::get_network_agent() { return BBLNetworkPlugin::instance().get_agent(); }
|
||||
|
||||
void NetworkAgent::apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>& printer_agent,
|
||||
const PrinterCallbacks& callbacks)
|
||||
void NetworkAgent::apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>& printer_agent, const PrinterCallbacks& callbacks)
|
||||
{
|
||||
if (!printer_agent) {
|
||||
return;
|
||||
@@ -201,408 +155,763 @@ void NetworkAgent::apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>&
|
||||
printer_agent->set_server_callback(callbacks.on_server_err_fn);
|
||||
}
|
||||
|
||||
std::shared_ptr<ICloudServiceAgent> NetworkAgent::get_cloud_agent(const std::string& provider) const
|
||||
{
|
||||
const auto& key = (provider.empty() || provider == ORCA_CLOUD_PROVIDER) ? ORCA_CLOUD_PROVIDER : provider;
|
||||
auto it = m_cloud_agents.find(key);
|
||||
return it != m_cloud_agents.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Instance methods - delegate to sub-agents
|
||||
// Shared agent methods
|
||||
// ============================================================================
|
||||
|
||||
int NetworkAgent::set_queue_on_main_fn(QueueOnMainFn fn, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
m_printer_callbacks.queue_on_main_fn = fn;
|
||||
|
||||
int ret = -1;
|
||||
if (cloud_agent)
|
||||
ret = cloud_agent->set_queue_on_main_fn(fn);
|
||||
if (m_printer_agent)
|
||||
m_printer_agent->set_queue_on_main_fn(fn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cloud agent methods
|
||||
// ============================================================================
|
||||
|
||||
int NetworkAgent::init_log()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->init_log();
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents, [](ICloudServiceAgent& cloud_agent) { return cloud_agent.init_log(); });
|
||||
}
|
||||
|
||||
int NetworkAgent::set_config_dir(std::string config_dir)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_config_dir(config_dir);
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents,
|
||||
[&config_dir](ICloudServiceAgent& cloud_agent) { return cloud_agent.set_config_dir(config_dir); });
|
||||
}
|
||||
|
||||
int NetworkAgent::set_cert_file(std::string folder, std::string filename)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_cert_file(folder, filename);
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents, [&folder, &filename](ICloudServiceAgent& cloud_agent) {
|
||||
return cloud_agent.set_cert_file(folder, filename);
|
||||
});
|
||||
}
|
||||
|
||||
int NetworkAgent::set_country_code(std::string country_code)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_country_code(country_code);
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents, [&country_code](ICloudServiceAgent& cloud_agent) {
|
||||
return cloud_agent.set_country_code(country_code);
|
||||
});
|
||||
}
|
||||
|
||||
int NetworkAgent::start()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->start();
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents, [](ICloudServiceAgent& cloud_agent) { return cloud_agent.start(); });
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn)
|
||||
int NetworkAgent::set_on_server_connected_fn(AppOnServerConnectedFn fn)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_ssdp_msg_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_ssdp_msg_fn(fn);
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents,
|
||||
[fn](ICloudServiceAgent& cloud_agent) { return cloud_agent.set_on_server_connected_fn(fn); });
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_user_login_fn(OnUserLoginFn fn)
|
||||
int NetworkAgent::set_on_http_error_fn(AppOnHttpErrorFn fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_on_user_login_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_printer_connected_fn(OnPrinterConnectedFn fn)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_printer_connected_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_printer_connected_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_server_connected_fn(OnServerConnectedFn fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_on_server_connected_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_http_error_fn(OnHttpErrorFn fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_on_http_error_fn(fn);
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents,
|
||||
[fn](ICloudServiceAgent& cloud_agent) { return cloud_agent.set_on_http_error_fn(fn); });
|
||||
}
|
||||
|
||||
int NetworkAgent::set_get_country_code_fn(GetCountryCodeFn fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_get_country_code_fn(fn);
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents,
|
||||
[fn](ICloudServiceAgent& cloud_agent) { return cloud_agent.set_get_country_code_fn(fn); });
|
||||
}
|
||||
|
||||
int NetworkAgent::change_user(std::string user_info, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->change_user(std::move(user_info));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_subscribe_failure_fn(GetSubscribeFailureFn fn)
|
||||
bool NetworkAgent::is_user_login(const std::string& provider)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_subscribe_failure_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_subscribe_failure_fn(fn);
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->is_user_login();
|
||||
return false;
|
||||
}
|
||||
|
||||
int NetworkAgent::user_logout(bool request, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->user_logout(request);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_message_fn(OnMessageFn fn)
|
||||
std::string NetworkAgent::get_user_id(const std::string& provider)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_message_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_message_fn(fn);
|
||||
return -1;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_id();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_user_message_fn(OnMessageFn fn)
|
||||
std::string NetworkAgent::get_user_name(const std::string& provider)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_user_message_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_user_message_fn(fn);
|
||||
return -1;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_name();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_local_connect_fn(OnLocalConnectedFn fn)
|
||||
std::string NetworkAgent::get_user_avatar(const std::string& provider)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_local_connect_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_local_connect_fn(fn);
|
||||
return -1;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_avatar();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_local_message_fn(OnMessageFn fn)
|
||||
std::string NetworkAgent::get_user_nickname(const std::string& provider)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_local_message_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_on_local_message_fn(fn);
|
||||
return -1;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_nickname();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
||||
std::string NetworkAgent::build_login_cmd(const std::string& provider)
|
||||
{
|
||||
// Set on both agents
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent;
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.queue_on_main_fn = fn;
|
||||
cloud_agent = m_cloud_agent;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->build_login_cmd();
|
||||
return "";
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
if (cloud_agent) ret = cloud_agent->set_queue_on_main_fn(fn);
|
||||
if (printer_agent) printer_agent->set_queue_on_main_fn(fn);
|
||||
return ret;
|
||||
std::string NetworkAgent::build_logout_cmd(const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->build_logout_cmd();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::build_login_info(const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->build_login_info();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_cloud_service_host(const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_cloud_service_host();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_cloud_login_url(const std::string& language, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_cloud_login_url(language);
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::connect_server()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->connect_server();
|
||||
return -1;
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents, [](ICloudServiceAgent& cloud_agent) { return cloud_agent.connect_server(); });
|
||||
}
|
||||
|
||||
bool NetworkAgent::is_server_connected()
|
||||
bool NetworkAgent::is_server_connected(const std::string& provider)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->is_server_connected();
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->is_server_connected();
|
||||
return false;
|
||||
}
|
||||
|
||||
int NetworkAgent::refresh_connection()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->refresh_connection();
|
||||
return invoke_on_all_cloud_agents(m_cloud_agents, [](ICloudServiceAgent& cloud_agent) { return cloud_agent.refresh_connection(); });
|
||||
}
|
||||
|
||||
void NetworkAgent::enable_multi_machine(bool enable, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
cloud_agent->enable_multi_machine(enable);
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_presets(user_presets);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_subscribe(std::string module)
|
||||
std::string NetworkAgent::request_setting_id(std::string name,
|
||||
std::map<std::string, std::string>* values_map,
|
||||
unsigned int* http_code,
|
||||
const std::string& provider)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->start_subscribe(module);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::stop_subscribe(std::string module)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->stop_subscribe(module);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::add_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->add_subscribe(dev_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::del_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->del_subscribe(dev_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NetworkAgent::enable_multi_machine(bool enable)
|
||||
{
|
||||
if (m_cloud_agent) m_cloud_agent->enable_multi_machine(enable);
|
||||
}
|
||||
|
||||
int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->send_message(dev_id, json_str, qos, flag);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->connect_printer(dev_id, dev_ip, username, password, use_ssl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::disconnect_printer()
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->disconnect_printer();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->send_message_to_printer(dev_id, json_str, qos, flag);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::check_cert()
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->check_cert();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NetworkAgent::install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
if (m_printer_agent) m_printer_agent->install_device_cert(dev_id, lan_only);
|
||||
}
|
||||
|
||||
bool NetworkAgent::start_discovery(bool start, bool sending)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->start_discovery(start, sending);
|
||||
return false;
|
||||
}
|
||||
|
||||
int NetworkAgent::change_user(std::string user_info)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->change_user(user_info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool NetworkAgent::is_user_login()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->is_user_login();
|
||||
return false;
|
||||
}
|
||||
|
||||
int NetworkAgent::user_logout(bool request)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->user_logout(request);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_user_id()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_id();
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->request_setting_id(std::move(name), values_map, http_code);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_user_name()
|
||||
int NetworkAgent::put_setting(std::string setting_id,
|
||||
std::string name,
|
||||
std::map<std::string, std::string>* values_map,
|
||||
unsigned int* http_code,
|
||||
const std::string& provider)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_name();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_user_avatar()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_avatar();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_user_nickname()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_nickname();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::build_login_cmd()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->build_login_cmd();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::build_logout_cmd()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->build_logout_cmd();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::build_login_info()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->build_login_info();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::ping_bind(std::string ping_code)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->ping_bind(ping_code);
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->put_setting(std::move(setting_id), std::move(name), values_map, http_code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
|
||||
int NetworkAgent::get_setting_list(std::string bundle_version, ProgressFn pro_fn, WasCancelledFn cancel_fn, const std::string& provider)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->bind_detect(dev_ip, sec_link, detect);
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_setting_list(std::move(bundle_version), pro_fn, cancel_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_setting_list2(
|
||||
std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn, WasCancelledFn cancel_fn, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_setting_list2(std::move(bundle_version), chk_fn, pro_fn, cancel_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::delete_setting(std::string setting_id, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->delete_setting(std::move(setting_id));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_my_message(type, after, limit, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::check_user_task_report(int* task_id, bool* printable, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->check_user_task_report(task_id, printable);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_print_info(unsigned int* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_print_info(http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_tasks(TaskQueryParams params, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_tasks(params, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_printer_firmware(std::move(dev_id), http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_task_plate_index(std::string task_id, int* plate_index, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_task_plate_index(std::move(task_id), plate_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_info(int* identifier, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_user_info(identifier);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_subtask_info(
|
||||
std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_subtask_info(std::move(subtask_id), task_json, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_slice_info(
|
||||
std::string project_id, std::string profile_id, int plate_index, std::string* slice_json, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_slice_info(std::move(project_id), std::move(profile_id), plate_index, slice_json);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::query_bind_status(std::vector<std::string> query_list,
|
||||
unsigned int* http_code,
|
||||
std::string* http_body,
|
||||
const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->query_bind_status(std::move(query_list), http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->modify_printer_name(std::move(dev_id), std::move(dev_name));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_camera_url(std::string dev_id, std::function<void(std::string)> callback, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_camera_url(std::move(dev_id), std::move(callback));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_design_staffpick(offset, limit, std::move(callback));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_publish(
|
||||
PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->start_publish(params, update_fn, cancel_fn, out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_publish_url(std::string* url, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_model_publish_url(url);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_subtask(task, getsub_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_mall_home_url(std::string* url, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_model_mall_home_url(url);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_mall_detail_url(std::string* url, std::string id, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_model_mall_detail_url(url, std::move(id));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_my_profile(std::string token, unsigned int* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_my_profile(std::move(token), http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_my_token(std::move(ticket), http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_enable(bool enable, const std::string& provider)
|
||||
{
|
||||
this->enable_track = enable;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_enable(enable);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_remove_files(const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_remove_files();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_event(std::string evt_key, std::string content, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_event(std::move(evt_key), std::move(content));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_header(std::string header, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_header(std::move(header));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_update_property(std::string name, std::string value, std::string type, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_update_property(std::move(name), std::move(value), std::move(type));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_get_property(std::string name, std::string& value, std::string type, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_get_property(std::move(name), value, std::move(type));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::put_model_mall_rating(int design_id,
|
||||
int score,
|
||||
std::string content,
|
||||
std::vector<std::string> images,
|
||||
unsigned int& http_code,
|
||||
std::string& http_error,
|
||||
const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->put_model_mall_rating(design_id, score, std::move(content), std::move(images), http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_oss_config(
|
||||
std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_oss_config(config, std::move(country_code), http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::put_rating_picture_oss(std::string& config,
|
||||
std::string& pic_oss_path,
|
||||
std::string model_id,
|
||||
int profile_id,
|
||||
unsigned int& http_code,
|
||||
std::string& http_error,
|
||||
const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->put_rating_picture_oss(config, pic_oss_path, std::move(model_id), profile_id, http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_mall_rating_result(
|
||||
int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_model_mall_rating_result(job_id, rating_result, http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_mw_user_preference(std::function<void(std::string)> callback, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_mw_user_preference(std::move(callback));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback, const std::string& provider)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->get_mw_user_4ulist(seed, limit, std::move(callback));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Printer agent methods
|
||||
// ============================================================================
|
||||
|
||||
int NetworkAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_ssdp_msg_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_ssdp_msg_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_printer_connected_fn(OnPrinterConnectedFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_printer_connected_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_printer_connected_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_subscribe_failure_fn(GetSubscribeFailureFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_subscribe_failure_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_subscribe_failure_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_message_fn(OnMessageFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_message_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_message_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_user_message_fn(OnMessageFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_user_message_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_user_message_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_local_connect_fn(OnLocalConnectedFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_local_connect_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_local_connect_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_on_local_message_fn(OnMessageFn fn)
|
||||
{
|
||||
m_printer_callbacks.on_local_message_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_on_local_message_fn(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
std::shared_ptr<IPrinterAgent> printer_agent;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_agent_mutex);
|
||||
m_printer_callbacks.on_server_err_fn = fn;
|
||||
printer_agent = m_printer_agent;
|
||||
}
|
||||
if (printer_agent) return printer_agent->set_server_callback(fn);
|
||||
m_printer_callbacks.on_server_err_fn = fn;
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_server_callback(fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->bind(dev_ip, dev_id, sec_link, timezone, improved, update_fn);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->send_message(dev_id, json_str, qos, flag);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->connect_printer(dev_id, dev_ip, username, password, use_ssl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::disconnect_printer()
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->disconnect_printer();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->send_message_to_printer(dev_id, json_str, qos, flag);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::check_cert()
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->check_cert();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NetworkAgent::install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
m_printer_agent->install_device_cert(dev_id, lan_only);
|
||||
}
|
||||
|
||||
bool NetworkAgent::start_discovery(bool start, bool sending)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_discovery(start, sending);
|
||||
return false;
|
||||
}
|
||||
|
||||
int NetworkAgent::ping_bind(std::string ping_code)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->ping_bind(ping_code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->bind_detect(dev_ip, sec_link, detect);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::bind(
|
||||
std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->bind(dev_ip, dev_id, sec_link, timezone, improved, update_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::unbind(std::string dev_id)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->unbind(dev_id);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->unbind(dev_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_cloud_service_host()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_cloud_service_host();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_cloud_login_url(const std::string& language)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_cloud_login_url(language);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_user_selected_machine()
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->get_user_selected_machine();
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->get_user_selected_machine();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::set_user_selected_machine(std::string dev_id)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->set_user_selected_machine(dev_id);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->set_user_selected_machine(dev_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_subscribe(std::string module)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_subscribe(std::move(module));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::stop_subscribe(std::string module)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->stop_subscribe(std::move(module));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::add_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->add_subscribe(std::move(dev_list));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::del_subscribe(std::vector<std::string> dev_list)
|
||||
{
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->del_subscribe(std::move(dev_list));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->start_print(params, update_fn, cancel_fn, wait_fn);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_print(params, update_fn, cancel_fn, wait_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, wait_fn);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, wait_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->start_local_print(params, update_fn, cancel_fn);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_local_print(params, update_fn, cancel_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->start_sdcard_print(params, update_fn, cancel_fn);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->start_sdcard_print(params, update_fn, cancel_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FilamentSyncMode NetworkAgent::get_filament_sync_mode() const
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->get_filament_sync_mode();
|
||||
return FilamentSyncMode::none; // Default when no agent
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->get_filament_sync_mode();
|
||||
return FilamentSyncMode::none;
|
||||
}
|
||||
|
||||
bool NetworkAgent::fetch_filament_info(std::string dev_id)
|
||||
@@ -613,250 +922,10 @@ bool NetworkAgent::fetch_filament_info(std::string dev_id)
|
||||
return false;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_presets(user_presets);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->request_setting_id(name, values_map, http_code);
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->put_setting(setting_id, name, values_map, http_code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_setting_list(std::string bundle_version, ProgressFn pro_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_setting_list(bundle_version, pro_fn, cancel_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_setting_list2(bundle_version, chk_fn, pro_fn, cancel_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::delete_setting(std::string setting_id)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->delete_setting(setting_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string NetworkAgent::get_studio_info_url()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_studio_info_url();
|
||||
return "";
|
||||
}
|
||||
|
||||
int NetworkAgent::set_extra_http_header(std::map<std::string, std::string> extra_headers)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->set_extra_http_header(extra_headers);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_my_message(type, after, limit, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::check_user_task_report(int* task_id, bool* printable)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->check_user_task_report(task_id, printable);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_print_info(unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_print_info(http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_tasks(TaskQueryParams params, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_tasks(params, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_printer_firmware(dev_id, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_task_plate_index(std::string task_id, int* plate_index)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_task_plate_index(task_id, plate_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_user_info(int* identifier)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_user_info(identifier);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
if (m_printer_agent) return m_printer_agent->request_bind_ticket(ticket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_subtask_info(subtask_id, task_json, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_slice_info(project_id, profile_id, plate_index, slice_json);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->query_bind_status(query_list, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::modify_printer_name(std::string dev_id, std::string dev_name)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->modify_printer_name(dev_id, dev_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_camera_url(std::string dev_id, std::function<void(std::string)> callback)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_camera_url(dev_id, callback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_design_staffpick(offset, limit, callback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->start_publish(params, update_fn, cancel_fn, out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_publish_url(std::string* url)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_model_publish_url(url);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_subtask(task, getsub_fn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_mall_home_url(std::string* url)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_model_mall_home_url(url);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_mall_detail_url(std::string* url, std::string id)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_model_mall_detail_url(url, id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_my_profile(std::string token, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_my_profile(token, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_my_token(ticket, http_code, http_body);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_enable(bool enable)
|
||||
{
|
||||
this->enable_track = enable;
|
||||
if (m_cloud_agent) return m_cloud_agent->track_enable(enable);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_remove_files()
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->track_remove_files();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_event(std::string evt_key, std::string content)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->track_event(evt_key, content);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_header(std::string header)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->track_header(header);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_update_property(std::string name, std::string value, std::string type)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->track_update_property(name, value, type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_get_property(std::string name, std::string& value, std::string type)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->track_get_property(name, value, type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::put_model_mall_rating(int design_id, int score, std::string content, std::vector<std::string> images, unsigned int& http_code, std::string& http_error)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->put_model_mall_rating(design_id, score, content, images, http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_oss_config(std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_oss_config(config, country_code, http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::put_rating_picture_oss(std::string& config, std::string& pic_oss_path, std::string model_id, int profile_id, unsigned int& http_code, std::string& http_error)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->put_rating_picture_oss(config, pic_oss_path, model_id, profile_id, http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_model_mall_rating_result(int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_model_mall_rating_result(job_id, rating_result, http_code, http_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_mw_user_preference(std::function<void(std::string)> callback)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_mw_user_preference(callback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback)
|
||||
{
|
||||
if (m_cloud_agent) return m_cloud_agent->get_mw_user_4ulist(seed, limit, callback);
|
||||
if (m_printer_agent)
|
||||
return m_printer_agent->request_bind_ticket(ticket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declaration
|
||||
class BBLNetworkPlugin;
|
||||
|
||||
//the NetworkAgent class
|
||||
// The NetworkAgent class
|
||||
class NetworkAgent
|
||||
{
|
||||
|
||||
public:
|
||||
// Static utility methods - delegate to BBLNetworkPlugin
|
||||
static std::string get_libpath_in_current_directory(std::string library_name);
|
||||
@@ -41,9 +42,6 @@ public:
|
||||
static void clear_load_error();
|
||||
static void set_load_error(const std::string& message, const std::string& technical_details, const std::string& attempted_path);
|
||||
|
||||
// Traditional constructor (uses BBL DLL via singleton)
|
||||
NetworkAgent(std::string log_dir);
|
||||
|
||||
// Sub-agent composition constructor (uses injected sub-agents)
|
||||
NetworkAgent(std::shared_ptr<ICloudServiceAgent> cloud_agent,
|
||||
std::shared_ptr<IPrinterAgent> printer_agent);
|
||||
@@ -51,38 +49,98 @@ public:
|
||||
~NetworkAgent();
|
||||
|
||||
// Sub-agent accessors
|
||||
std::shared_ptr<ICloudServiceAgent> get_cloud_agent() const { return m_cloud_agent; }
|
||||
std::shared_ptr<ICloudServiceAgent> get_cloud_agent(const std::string& provider = ORCA_CLOUD_PROVIDER) const;
|
||||
std::shared_ptr<IPrinterAgent> get_printer_agent() const { return m_printer_agent; }
|
||||
|
||||
// Set the printer agent (for dynamic agent switching)
|
||||
// Shared agent management
|
||||
void add_cloud_agent(const std::string& provider, std::shared_ptr<ICloudServiceAgent> agent);
|
||||
void set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent);
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
// Instance methods - delegate to sub-agents or BBLNetworkPlugin
|
||||
// Get underlying agent handle from BBLNetworkPlugin
|
||||
void* get_network_agent();
|
||||
|
||||
// Cloud agent methods
|
||||
// These methods will be forwarded to all cloud agents
|
||||
int init_log();
|
||||
int set_config_dir(std::string config_dir);
|
||||
int set_cert_file(std::string folder, std::string filename);
|
||||
int set_country_code(std::string country_code);
|
||||
int start();
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn);
|
||||
int set_on_user_login_fn(OnUserLoginFn fn);
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn);
|
||||
int set_on_server_connected_fn(OnServerConnectedFn fn);
|
||||
int set_on_http_error_fn(OnHttpErrorFn fn);
|
||||
int set_on_server_connected_fn(AppOnServerConnectedFn fn);
|
||||
int set_on_http_error_fn(AppOnHttpErrorFn fn);
|
||||
int set_get_country_code_fn(GetCountryCodeFn fn);
|
||||
int connect_server();
|
||||
int refresh_connection();
|
||||
|
||||
int change_user(std::string user_info, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int user_logout(bool request = false, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string get_user_id(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string get_user_name(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string get_user_avatar(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string get_user_nickname(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string build_login_cmd(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string build_logout_cmd(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string build_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string get_cloud_service_host(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string get_cloud_login_url(const std::string& language = "", const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
bool is_server_connected(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
void enable_multi_machine(bool enable, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
// Profile synchronization methods
|
||||
// NOTE: this should always call only OrcaCloud
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int delete_setting(std::string setting_id, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
int get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int check_user_task_report(int* task_id, bool* printable, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_user_print_info(unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_user_tasks(TaskQueryParams params, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_task_plate_index(std::string task_id, int* plate_index, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_user_info(int* identifier, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_model_publish_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_model_mall_home_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_model_mall_detail_url(std::string* url, std::string id, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_enable(bool enable, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_remove_files(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_event(std::string evt_key, std::string content, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_header(std::string header, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_update_property(std::string name, std::string value, std::string type = "string", const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_get_property(std::string name, std::string& value, std::string type = "string", const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int put_model_mall_rating(int design_id, int score, std::string content, std::vector<std::string> images, unsigned int &http_code, std::string &http_error, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_oss_config(std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int put_rating_picture_oss(std::string &config, std::string &pic_oss_path, std::string model_id, int profile_id, unsigned int &http_code, std::string &http_error, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_model_mall_rating_result(int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
bool get_track_enable() { return enable_track; }
|
||||
int get_mw_user_preference(std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
// Printer agent methods
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn);
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn);
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn);
|
||||
int set_on_message_fn(OnMessageFn fn);
|
||||
int set_on_user_message_fn(OnMessageFn fn);
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn);
|
||||
int set_on_local_message_fn(OnMessageFn fn);
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn);
|
||||
int connect_server();
|
||||
bool is_server_connected();
|
||||
int refresh_connection();
|
||||
int start_subscribe(std::string module);
|
||||
int stop_subscribe(std::string module);
|
||||
int add_subscribe(std::vector<std::string> dev_list);
|
||||
int del_subscribe(std::vector<std::string> dev_list);
|
||||
void enable_multi_machine(bool enable);
|
||||
int set_server_callback(OnServerErrFn fn);
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag);
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
int disconnect_printer();
|
||||
@@ -90,25 +148,16 @@ public:
|
||||
int check_cert();
|
||||
void install_device_cert(std::string dev_id, bool lan_only);
|
||||
bool start_discovery(bool start, bool sending);
|
||||
int change_user(std::string user_info);
|
||||
bool is_user_login();
|
||||
int user_logout(bool request = false);
|
||||
std::string get_user_id();
|
||||
std::string get_user_name();
|
||||
std::string get_user_avatar();
|
||||
std::string get_user_nickname();
|
||||
std::string build_login_cmd();
|
||||
std::string build_logout_cmd();
|
||||
std::string build_login_info();
|
||||
int ping_bind(std::string ping_code);
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect);
|
||||
int set_server_callback(OnServerErrFn fn);
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
|
||||
int unbind(std::string dev_id);
|
||||
std::string get_cloud_service_host();
|
||||
std::string get_cloud_login_url(const std::string& language = "");
|
||||
std::string get_user_selected_machine();
|
||||
int set_user_selected_machine(std::string dev_id);
|
||||
int start_subscribe(std::string module);
|
||||
int stop_subscribe(std::string module);
|
||||
int add_subscribe(std::vector<std::string> dev_list);
|
||||
int del_subscribe(std::vector<std::string> dev_list);
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
@@ -116,52 +165,7 @@ public:
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
FilamentSyncMode get_filament_sync_mode() const;
|
||||
bool fetch_filament_info(std::string dev_id);
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets);
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
|
||||
int delete_setting(std::string setting_id);
|
||||
std::string get_studio_info_url();
|
||||
int set_extra_http_header(std::map<std::string, std::string> extra_headers);
|
||||
int get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body);
|
||||
int check_user_task_report(int* task_id, bool* printable);
|
||||
int get_user_print_info(unsigned int* http_code, std::string* http_body);
|
||||
int get_user_tasks(TaskQueryParams params, std::string* http_body);
|
||||
int get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body);
|
||||
int get_task_plate_index(std::string task_id, int* plate_index);
|
||||
int get_user_info(int* identifier);
|
||||
int request_bind_ticket(std::string* ticket);
|
||||
int get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body);
|
||||
int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json);
|
||||
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body);
|
||||
int modify_printer_name(std::string dev_id, std::string dev_name);
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback);
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback);
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out);
|
||||
int get_model_publish_url(std::string* url);
|
||||
int get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn);
|
||||
int get_model_mall_home_url(std::string* url);
|
||||
int get_model_mall_detail_url(std::string* url, std::string id);
|
||||
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body);
|
||||
int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body);
|
||||
int track_enable(bool enable);
|
||||
int track_remove_files();
|
||||
int track_event(std::string evt_key, std::string content);
|
||||
int track_header(std::string header);
|
||||
int track_update_property(std::string name, std::string value, std::string type = "string");
|
||||
int track_get_property(std::string name, std::string& value, std::string type = "string");
|
||||
int put_model_mall_rating(int design_id, int score, std::string content, std::vector<std::string> images, unsigned int &http_code, std::string &http_error);
|
||||
int get_oss_config(std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error);
|
||||
int put_rating_picture_oss(std::string &config, std::string &pic_oss_path, std::string model_id, int profile_id, unsigned int &http_code, std::string &http_error);
|
||||
int get_model_mall_rating_result(int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error);
|
||||
bool get_track_enable() { return enable_track; }
|
||||
|
||||
int get_mw_user_preference(std::function<void(std::string)> callback);
|
||||
int get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback);
|
||||
|
||||
// Get underlying agent handle from BBLNetworkPlugin
|
||||
void* get_network_agent();
|
||||
|
||||
private:
|
||||
struct PrinterCallbacks {
|
||||
@@ -178,13 +182,14 @@ private:
|
||||
|
||||
void apply_printer_callbacks(const std::shared_ptr<IPrinterAgent>& printer_agent,
|
||||
const PrinterCallbacks& callbacks);
|
||||
|
||||
mutable std::mutex m_agent_mutex; // Protect agent swapping
|
||||
PrinterCallbacks m_printer_callbacks;
|
||||
bool enable_track = false;
|
||||
|
||||
// Sub-agent composition (for Orca/BBL mixed mode)
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
// Sub-agent composition
|
||||
// We support dynamic switching of printer agents (e.g. for different printer types), but the cloud agent is fixed at construction since
|
||||
// it's tied to the user's cloud account OrcaCloudServiceAgent is designed to be the primary cloud agent, but we support the possibility
|
||||
// of adding third-party cloud agents (e.g. BBLCloudServiceAgent) and delegating calls to them as needed
|
||||
std::map<std::string, std::shared_ptr<ICloudServiceAgent>> m_cloud_agents;
|
||||
std::shared_ptr<IPrinterAgent> m_printer_agent;
|
||||
std::string m_printer_agent_id;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user