mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-22 00:12:34 +00:00
Modularize printer agent architecture and add filament sync for third-party AMS-style systems (#12086)
# Description This PR lays the groundwork for future OrcaSlicer ↔ printer connectivity enhancements by modularizing the network agent architecture. It also adds experimental filament info sync from printers that have a material station or multi-tool system. ## Architecture Changes - **Introduce `IPrinterAgent` interface** — an abstraction layer that allows per-machine printer agent switching at runtime, decoupling printer communication from the monolithic `NetworkAgent` - **Introduce `ICloudServiceAgent` interface** — separates cloud service logic from printer-level communication - **Extract `OrcaCloudServiceAgent`** — moves Orca cloud service logic into its own implementation behind `ICloudServiceAgent` - **Extract `OrcaPrinterAgent`** — wraps the existing BBL printer communication behind `IPrinterAgent` - **Add `NetworkAgentFactory`** — factory for creating the appropriate printer agent per machine - **Refactor `NetworkAgent`** — slimmed down from monolithic class to a thinner coordination layer ## New Printer Agents - **`MoonrakerPrinterAgent`** — Klipper/Moonraker-based printers - **`QidiPrinterAgent`** — Qidi printers (with Qidi filament box support) - **`SnapmakerPrinterAgent`** — Snapmaker printers with filament sync ## Filament Sync (Experimental) Syncs filament information from printers equipped with AMS-style material systems or multi-tool changers: - Qidi printers with Qidi box - Armored Turtle (AFC) box via Moonraker - Snapmaker material station For Qidi printers with Qidi box: <img width="1200" height="762" alt="Screenshot 2026-01-27 at 20 30 55" src="https://github.com/user-attachments/assets/155a164f-cd08-40b0-b62b-c3ab7378224e" /> Armored Turtle box: <img width="1135" height="805" alt="Screenshot 2026-01-27 at 20 32 58" src="https://github.com/user-attachments/assets/50f6618e-eb54-46db-8e01-1197a005fbf0" /> # Screenshots/Recordings/Graphs [filasync.webm](https://github.com/user-attachments/assets/e6bb7f04-8312-4014-b237-6bd3ef792215) ## Tests <!-- Please describe the tests that you have conducted to verify the changes made in this PR. -->
This commit is contained in:
@@ -9,67 +9,22 @@ OrcaSlicer is an open-source 3D slicer application forked from Bambu Studio, bui
|
||||
## Build Commands
|
||||
|
||||
### Building on Windows
|
||||
**Always use this command to build the project when testing build issues on Windows.**
|
||||
```bash
|
||||
# Build everything
|
||||
build_release_vs2022.bat
|
||||
|
||||
# Build with debug symbols
|
||||
build_release_vs2022.bat debug
|
||||
|
||||
# Build only dependencies
|
||||
build_release_vs2022.bat deps
|
||||
|
||||
# Build only slicer (after deps are built)
|
||||
build_release_vs2022.bat slicer
|
||||
|
||||
|
||||
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
|
||||
# Build everything (dependencies and slicer)
|
||||
./build_release_macos.sh
|
||||
|
||||
# Build only dependencies
|
||||
./build_release_macos.sh -d
|
||||
|
||||
# Build only slicer (after deps are built)
|
||||
./build_release_macos.sh -s
|
||||
|
||||
# Use Ninja generator for faster builds
|
||||
./build_release_macos.sh -x
|
||||
|
||||
# Build for specific architecture
|
||||
./build_release_macos.sh -a arm64 # or x86_64 or universal
|
||||
|
||||
# Build for specific macOS version target
|
||||
./build_release_macos.sh -t 11.3
|
||||
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
|
||||
# First time setup - install system dependencies
|
||||
./build_linux.sh -u
|
||||
cmake --build build/arm64 --config RelWithDebInfo --target all --
|
||||
|
||||
# Build dependencies and slicer
|
||||
./build_linux.sh -dsi
|
||||
|
||||
# Build everything (alternative)
|
||||
./build_linux.sh -dsi
|
||||
|
||||
# Individual options:
|
||||
./build_linux.sh -d # dependencies only
|
||||
./build_linux.sh -s # slicer only
|
||||
./build_linux.sh -i # build AppImage
|
||||
|
||||
# Performance and debug options:
|
||||
./build_linux.sh -j N # limit to N cores
|
||||
./build_linux.sh -1 # single core build
|
||||
./build_linux.sh -b # Debug build
|
||||
./build_linux.sh -e # RelWithDebInfo build
|
||||
./build_linux.sh -c # clean build
|
||||
./build_linux.sh -r # skip RAM/disk checks
|
||||
./build_linux.sh -l # use Clang instead of GCC
|
||||
```
|
||||
### Build test:
|
||||
|
||||
@@ -91,6 +46,7 @@ cmake --build build/arm64 --config RelWithDebInfo --target all --
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Build System
|
||||
- Uses CMake with minimum version 3.13 (maximum 3.31.x on Windows)
|
||||
- Primary build directory: `build/`
|
||||
|
||||
@@ -0,0 +1,927 @@
|
||||
<!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
+477
@@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for MoonrakerPrinterAgent filament sync feature.
|
||||
Inserts/deletes/modifies random lane data in Moonraker database,
|
||||
then reads back and displays with colored output.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import random
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
|
||||
# Configuration
|
||||
DEFAULT_HOST = "192.168.88.9"
|
||||
DEFAULT_PORT = 7125
|
||||
NAMESPACE = "lane_data"
|
||||
LANE_KEYS = [f"lane{i}" for i in range(1, 9)] # lane1-lane8
|
||||
MATERIALS = ["PLA", "ABS", "PETG", "ASA", "ASA Sparkle", "TPU", ""]
|
||||
|
||||
# Material default temperatures (None = use null)
|
||||
MATERIAL_TEMPS = {
|
||||
"PLA": {"nozzle": 210, "bed": 60},
|
||||
"ABS": {"nozzle": 240, "bed": 100},
|
||||
"PETG": {"nozzle": 235, "bed": 80},
|
||||
"ASA": {"nozzle": 245, "bed": 105},
|
||||
"ASA Sparkle":{"nozzle": 245, "bed": 105},
|
||||
"TPU": {"nozzle": 220, "bed": 50},
|
||||
"": {"nozzle": None, "bed": None},
|
||||
}
|
||||
|
||||
def test_connection(host, port, api_key=None, verbose=False):
|
||||
"""Test basic connectivity to Moonraker."""
|
||||
url = f"http://{host}:{port}/server/info"
|
||||
headers = {"X-Api-Key": api_key} if api_key else {}
|
||||
|
||||
if verbose:
|
||||
print(f" Testing: GET {url}")
|
||||
|
||||
try:
|
||||
resp = requests.get(url, headers=headers, timeout=10)
|
||||
if verbose:
|
||||
print(f" Response: HTTP {resp.status_code}")
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if verbose:
|
||||
print(f" Moonraker version: {data.get('result', {}).get('moonraker_version', 'unknown')}")
|
||||
return True
|
||||
else:
|
||||
print(f" Server returned HTTP {resp.status_code}")
|
||||
if verbose:
|
||||
print(f" Response: {resp.text[:500]}")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
print(f" Connection error: {e}")
|
||||
return False
|
||||
except requests.exceptions.Timeout:
|
||||
print(f" Connection timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" Error: {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
def hex_to_rgb(hex_color):
|
||||
"""Convert hex color to RGB tuple."""
|
||||
hex_color = hex_color.lstrip('#')
|
||||
if hex_color.startswith('0x') or hex_color.startswith('0X'):
|
||||
hex_color = hex_color[2:]
|
||||
if len(hex_color) == 6:
|
||||
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||
return (128, 128, 128) # Default gray
|
||||
|
||||
def color_block(hex_color):
|
||||
"""Return ANSI color block for terminal display."""
|
||||
r, g, b = hex_to_rgb(hex_color)
|
||||
return f"\033[48;2;{r};{g};{b}m \033[0m"
|
||||
|
||||
def random_color():
|
||||
"""Generate random hex color, occasionally returning empty or '#None' like real data."""
|
||||
r = random.random()
|
||||
if r < 0.1:
|
||||
return "" # Empty color (empty lane)
|
||||
if r < 0.15:
|
||||
return "#None" # Observed in real data for unknown colors
|
||||
return "#{:06x}".format(random.randint(0, 0xFFFFFF))
|
||||
|
||||
def get_lane_data(host, port, api_key=None):
|
||||
"""Fetch all lane data from Moonraker database."""
|
||||
url = f"http://{host}:{port}/server/database/item"
|
||||
params = {"namespace": NAMESPACE}
|
||||
headers = {"X-Api-Key": api_key} if api_key else {}
|
||||
|
||||
try:
|
||||
resp = requests.get(url, params=params, headers=headers, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
return data.get("result", {}).get("value", {})
|
||||
elif resp.status_code == 404:
|
||||
return {} # Namespace doesn't exist yet
|
||||
else:
|
||||
print(f"Error fetching lane data: HTTP {resp.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error fetching lane data: {e}")
|
||||
return None
|
||||
|
||||
def set_lane_data(host, port, lane_key, lane_data, api_key=None):
|
||||
"""Set lane data in Moonraker database."""
|
||||
url = f"http://{host}:{port}/server/database/item"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if api_key:
|
||||
headers["X-Api-Key"] = api_key
|
||||
|
||||
payload = {
|
||||
"namespace": NAMESPACE,
|
||||
"key": lane_key,
|
||||
"value": lane_data
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.post(url, json=payload, headers=headers, timeout=5)
|
||||
return resp.status_code == 200
|
||||
except Exception as e:
|
||||
print(f"Error setting lane data: {e}")
|
||||
return False
|
||||
|
||||
def delete_lane_data(host, port, lane_key, api_key=None):
|
||||
"""Delete lane data from Moonraker database."""
|
||||
url = f"http://{host}:{port}/server/database/item"
|
||||
params = {"namespace": NAMESPACE, "key": lane_key}
|
||||
headers = {"X-Api-Key": api_key} if api_key else {}
|
||||
|
||||
try:
|
||||
resp = requests.delete(url, params=params, headers=headers, timeout=5)
|
||||
return resp.status_code == 200
|
||||
except Exception as e:
|
||||
print(f"Error deleting lane data: {e}")
|
||||
return False
|
||||
|
||||
def display_lanes(lanes):
|
||||
"""Display lane data with color blocks."""
|
||||
print("\n" + "="*70)
|
||||
print("CURRENT LANE DATA")
|
||||
print("="*70)
|
||||
|
||||
if not lanes:
|
||||
print(" (no lanes configured)")
|
||||
return
|
||||
|
||||
# Sort by lane number
|
||||
sorted_lanes = sorted(lanes.items(),
|
||||
key=lambda x: int(x[1].get("lane", "0")) if x[1].get("lane", "").isdigit() else 0)
|
||||
|
||||
for lane_key, data in sorted_lanes:
|
||||
lane_num = data.get("lane", "?")
|
||||
material = data.get("material", "") or "(empty)"
|
||||
color = data.get("color", "")
|
||||
bed_temp = data.get("bed_temp")
|
||||
nozzle_temp = data.get("nozzle_temp")
|
||||
spool_id = data.get("spool_id")
|
||||
|
||||
# Show color block only for valid hex colors
|
||||
if color and color.startswith("#") and color != "#None" and len(color) == 7:
|
||||
block = color_block(color)
|
||||
else:
|
||||
block = " " # No color block
|
||||
|
||||
bed_str = f"{bed_temp}°C" if bed_temp is not None else "-"
|
||||
noz_str = f"{nozzle_temp}°C" if nozzle_temp is not None else "-"
|
||||
spool_str = f" Spool: {spool_id}" if spool_id is not None else ""
|
||||
color_str = color if color else "(none)"
|
||||
|
||||
print(f" {lane_key} (T{lane_num}): {block} {color_str:10s} {material:12s} "
|
||||
f"Nozzle: {noz_str:6s} Bed: {bed_str:5s}{spool_str}")
|
||||
|
||||
print("="*70 + "\n")
|
||||
|
||||
def make_lane_entry(tool_number, material=None):
|
||||
"""Generate a lane data entry matching real Moonraker AFC structure."""
|
||||
if material is None:
|
||||
material = random.choice(MATERIALS)
|
||||
temps = MATERIAL_TEMPS[material]
|
||||
color = random_color()
|
||||
|
||||
bed = None
|
||||
nozzle = None
|
||||
if temps["bed"] is not None:
|
||||
bed = temps["bed"] + random.randint(-5, 5)
|
||||
if temps["nozzle"] is not None:
|
||||
nozzle = temps["nozzle"] + random.randint(-10, 10)
|
||||
|
||||
spool_id = random.choice([None, random.randint(1, 50)])
|
||||
|
||||
return {
|
||||
"color": color,
|
||||
"material": material,
|
||||
"bed_temp": bed,
|
||||
"nozzle_temp": nozzle,
|
||||
"scan_time": "",
|
||||
"td": "",
|
||||
"lane": str(tool_number),
|
||||
"spool_id": spool_id,
|
||||
}
|
||||
|
||||
def get_used_tool_numbers(host, port, api_key=None, exclude_key=None):
|
||||
"""Get set of tool numbers currently in use."""
|
||||
lanes = get_lane_data(host, port, api_key) or {}
|
||||
used = set()
|
||||
for key, data in lanes.items():
|
||||
if key == exclude_key:
|
||||
continue
|
||||
lane_val = data.get("lane", "")
|
||||
if lane_val.isdigit():
|
||||
used.add(int(lane_val))
|
||||
return used
|
||||
|
||||
def pick_available_tool_number(used_tool_numbers):
|
||||
"""Pick a random tool number (0-7) not already in use. Returns None if all taken."""
|
||||
available = [n for n in range(8) if n not in used_tool_numbers]
|
||||
if not available:
|
||||
return None
|
||||
return random.choice(available)
|
||||
|
||||
def fix_duplicate_lanes(host, port, lanes, api_key=None):
|
||||
"""Detect and fix duplicate tool numbers in existing lane data.
|
||||
|
||||
Returns the updated lane data after fixes.
|
||||
"""
|
||||
if not lanes:
|
||||
return lanes
|
||||
|
||||
# Map tool number -> list of lane keys using it
|
||||
tool_to_keys = {}
|
||||
for key, data in lanes.items():
|
||||
tool = data.get("lane", "")
|
||||
if tool == "":
|
||||
continue
|
||||
tool_to_keys.setdefault(tool, []).append(key)
|
||||
|
||||
# Find duplicates
|
||||
duplicates = {tool: keys for tool, keys in tool_to_keys.items() if len(keys) > 1}
|
||||
if not duplicates:
|
||||
return lanes
|
||||
|
||||
print("DUPLICATE TOOL NUMBERS DETECTED:")
|
||||
for tool, keys in duplicates.items():
|
||||
print(f" Tool T{tool} used by: {', '.join(keys)}")
|
||||
|
||||
# Collect all used tool numbers
|
||||
used = set()
|
||||
for tool, keys in tool_to_keys.items():
|
||||
if tool.isdigit():
|
||||
used.add(int(tool))
|
||||
|
||||
# Fix: keep the first key for each tool, reassign the rest
|
||||
print("\nFixing duplicates...")
|
||||
for tool, keys in duplicates.items():
|
||||
# Keep the first one, reassign the rest
|
||||
for key in keys[1:]:
|
||||
available = [n for n in range(8) if n not in used]
|
||||
if not available:
|
||||
print(f" {key}: cannot fix, no available tool numbers!")
|
||||
continue
|
||||
|
||||
new_tool = available[0]
|
||||
used.add(new_tool)
|
||||
|
||||
lanes[key]["lane"] = str(new_tool)
|
||||
if set_lane_data(host, port, key, lanes[key], api_key):
|
||||
print(f" {key}: T{tool} -> T{new_tool}")
|
||||
else:
|
||||
print(f" {key}: FAILED to update")
|
||||
|
||||
print()
|
||||
return lanes
|
||||
|
||||
def perform_random_operations(host, port, api_key=None, num_ops=5):
|
||||
"""Perform random insert/modify/delete operations."""
|
||||
operations = ["insert", "modify", "delete"]
|
||||
|
||||
print(f"\nPerforming {num_ops} random operations...")
|
||||
print("-"*50)
|
||||
|
||||
for i in range(num_ops):
|
||||
op = random.choice(operations)
|
||||
lane_key = random.choice(LANE_KEYS)
|
||||
|
||||
if op in ("insert", "modify"):
|
||||
# Get currently used tool numbers, excluding this key (ok to reuse its own)
|
||||
used = get_used_tool_numbers(host, port, api_key, exclude_key=lane_key)
|
||||
tool_num = pick_available_tool_number(used)
|
||||
if tool_num is None:
|
||||
print(f" [{op.upper()}] {lane_key}: SKIPPED (all tool numbers in use)")
|
||||
continue
|
||||
|
||||
lane_data = make_lane_entry(tool_num)
|
||||
action = "INSERT" if op == "insert" else "MODIFY"
|
||||
color = lane_data["color"]
|
||||
material = lane_data["material"] or "(empty)"
|
||||
tool = lane_data["lane"]
|
||||
|
||||
if color and color.startswith("#") and color != "#None" and len(color) == 7:
|
||||
block = color_block(color)
|
||||
else:
|
||||
block = " "
|
||||
|
||||
if set_lane_data(host, port, lane_key, lane_data, api_key):
|
||||
print(f" [{action}] {lane_key} (T{tool}): {block} {color or '(none)'} "
|
||||
f"{material} spool={lane_data['spool_id']}")
|
||||
else:
|
||||
print(f" [{action}] {lane_key}: FAILED")
|
||||
|
||||
elif op == "delete":
|
||||
if delete_lane_data(host, port, lane_key, api_key):
|
||||
print(f" [DELETE] {lane_key}")
|
||||
else:
|
||||
print(f" [DELETE] {lane_key}: FAILED (may not exist)")
|
||||
|
||||
time.sleep(0.1) # Small delay between operations
|
||||
|
||||
print("-"*50)
|
||||
|
||||
def load_lanes_from_file(filepath, host, port, api_key=None):
|
||||
"""Load lane data from a JSON file and overwrite all lanes on the printer.
|
||||
|
||||
Accepts either the raw Moonraker response format:
|
||||
{"result": {"namespace": "lane_data", "value": {"lane1": {...}, ...}}}
|
||||
or the plain value object:
|
||||
{"lane1": {...}, "lane2": {...}, ...}
|
||||
"""
|
||||
try:
|
||||
with open(filepath, "r") as f:
|
||||
data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: file not found: {filepath}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: invalid JSON in {filepath}: {e}")
|
||||
return False
|
||||
|
||||
# Accept both wrapped and unwrapped formats
|
||||
if "result" in data and "value" in data.get("result", {}):
|
||||
lanes = data["result"]["value"]
|
||||
else:
|
||||
lanes = data
|
||||
|
||||
if not isinstance(lanes, dict):
|
||||
print(f"Error: expected object with lane keys, got {type(lanes).__name__}")
|
||||
return False
|
||||
|
||||
# Validate no duplicate tool numbers
|
||||
tool_to_keys = {}
|
||||
for key, entry in lanes.items():
|
||||
tool = entry.get("lane", "")
|
||||
if tool:
|
||||
tool_to_keys.setdefault(tool, []).append(key)
|
||||
dupes = {t: keys for t, keys in tool_to_keys.items() if len(keys) > 1}
|
||||
if dupes:
|
||||
print("Error: input JSON has duplicate tool numbers:")
|
||||
for tool, keys in dupes.items():
|
||||
print(f" Tool T{tool} used by: {', '.join(keys)}")
|
||||
return False
|
||||
|
||||
print(f"Loading {len(lanes)} lane(s) from {filepath}...")
|
||||
|
||||
# Clear all existing lanes first
|
||||
print(" Clearing existing lanes...")
|
||||
for lane_key in LANE_KEYS:
|
||||
delete_lane_data(host, port, lane_key, api_key)
|
||||
|
||||
# Write each lane from the file
|
||||
ok = True
|
||||
for lane_key, lane_data in lanes.items():
|
||||
if set_lane_data(host, port, lane_key, lane_data, api_key):
|
||||
tool = lane_data.get("lane", "?")
|
||||
material = lane_data.get("material", "") or "(empty)"
|
||||
color = lane_data.get("color", "")
|
||||
if color and color.startswith("#") and color != "#None" and len(color) == 7:
|
||||
block = color_block(color)
|
||||
else:
|
||||
block = " "
|
||||
print(f" [LOAD] {lane_key} (T{tool}): {block} {color or '(none)'} {material}")
|
||||
else:
|
||||
print(f" [LOAD] {lane_key}: FAILED")
|
||||
ok = False
|
||||
|
||||
return ok
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Test Moonraker lane data for MoonrakerPrinterAgent filament sync"
|
||||
)
|
||||
parser.add_argument("--host", default=DEFAULT_HOST,
|
||||
help=f"Moonraker host (default: {DEFAULT_HOST})")
|
||||
parser.add_argument("--port", type=int, default=DEFAULT_PORT,
|
||||
help=f"Moonraker port (default: {DEFAULT_PORT})")
|
||||
parser.add_argument("--api-key", help="Moonraker API key (if required)")
|
||||
parser.add_argument("--ops", type=int, default=5,
|
||||
help="Number of random operations (default: 5)")
|
||||
parser.add_argument("--clear", action="store_true",
|
||||
help="Clear all lane data before starting")
|
||||
parser.add_argument("--read-only", action="store_true",
|
||||
help="Only read and display current lane data")
|
||||
parser.add_argument("--load", metavar="FILE",
|
||||
help="Load lane data from JSON file and overwrite printer lanes")
|
||||
parser.add_argument("--verbose", "-v", action="store_true",
|
||||
help="Verbose output for debugging")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"\nConnecting to Moonraker at {args.host}:{args.port}...")
|
||||
|
||||
# First test basic connectivity
|
||||
if not test_connection(args.host, args.port, args.api_key, args.verbose):
|
||||
print("\nFailed to connect to Moonraker!")
|
||||
print("\nTroubleshooting:")
|
||||
print(f" 1. Check if Moonraker is running on {args.host}")
|
||||
print(f" 2. Verify port {args.port} is correct (default Moonraker port is 7125)")
|
||||
print(f" 3. Try: curl http://{args.host}:{args.port}/server/info")
|
||||
print(f" 4. Check if API key is required (--api-key)")
|
||||
return 1
|
||||
|
||||
print("Connected!")
|
||||
|
||||
# Now fetch lane data
|
||||
current = get_lane_data(args.host, args.port, args.api_key)
|
||||
if current is None:
|
||||
print("Connected to Moonraker but failed to fetch lane data!")
|
||||
return 1
|
||||
|
||||
# Check for and fix duplicate tool numbers
|
||||
current = fix_duplicate_lanes(args.host, args.port, current, args.api_key)
|
||||
|
||||
# Show current state
|
||||
display_lanes(current)
|
||||
|
||||
if args.read_only:
|
||||
return 0
|
||||
|
||||
# Load from JSON file if requested
|
||||
if args.load:
|
||||
if not load_lanes_from_file(args.load, args.host, args.port, args.api_key):
|
||||
return 1
|
||||
final = get_lane_data(args.host, args.port, args.api_key)
|
||||
display_lanes(final)
|
||||
if final is not None:
|
||||
print("RAW JSON:")
|
||||
print(json.dumps({"result": {"namespace": NAMESPACE, "key": None, "value": final}}, indent=2))
|
||||
print()
|
||||
return 0
|
||||
|
||||
# Clear if requested
|
||||
if args.clear:
|
||||
print("Clearing all lane data...")
|
||||
for lane_key in LANE_KEYS:
|
||||
delete_lane_data(args.host, args.port, lane_key, args.api_key)
|
||||
print("Cleared!")
|
||||
display_lanes({})
|
||||
|
||||
# Perform random operations
|
||||
perform_random_operations(args.host, args.port, args.api_key, args.ops)
|
||||
|
||||
# Read back and display final state
|
||||
final = get_lane_data(args.host, args.port, args.api_key)
|
||||
display_lanes(final)
|
||||
|
||||
# Print raw JSON
|
||||
if final is not None:
|
||||
print("RAW JSON:")
|
||||
print(json.dumps({"result": {"namespace": NAMESPACE, "key": None, "value": final}}, indent=2))
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -300,6 +300,13 @@ void AppConfig::set_defaults()
|
||||
if (get("allow_abnormal_storage").empty()) {
|
||||
set_bool("allow_abnormal_storage", false);
|
||||
}
|
||||
#ifdef __linux__
|
||||
if (get(SETTING_USE_ENCRYPTED_TOKEN_FILE).empty())
|
||||
set_bool(SETTING_USE_ENCRYPTED_TOKEN_FILE, true);
|
||||
#else
|
||||
if (get(SETTING_USE_ENCRYPTED_TOKEN_FILE).empty())
|
||||
set_bool(SETTING_USE_ENCRYPTED_TOKEN_FILE, false);
|
||||
#endif
|
||||
|
||||
if(get("check_stable_update_only").empty()) {
|
||||
set_bool("check_stable_update_only", false);
|
||||
|
||||
@@ -28,6 +28,7 @@ using namespace nlohmann;
|
||||
#define SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS "network_plugin_skipped_versions"
|
||||
#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 BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01"
|
||||
|
||||
#define SUPPORT_DARK_MODE
|
||||
|
||||
@@ -1017,7 +1017,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode",
|
||||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure",
|
||||
"best_object_pos", "head_wrap_detect_zone",
|
||||
"host_type", "print_host", "printhost_apikey", "bbl_use_printhost",
|
||||
"host_type", "print_host", "printhost_apikey", "bbl_use_printhost", "printer_agent",
|
||||
"print_host_webui",
|
||||
"printhost_cafile","printhost_port","printhost_authorization_type",
|
||||
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format",
|
||||
@@ -1502,7 +1502,7 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map<std
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " uploading user preset name is: " << preset.name << "and create filament_id is: " << preset.filament_id
|
||||
<< " and base_id is: " << preset.base_id;
|
||||
key_values[BBL_JSON_KEY_UPDATE_TIME] = std::to_string(preset.updated_time);
|
||||
key_values[ORCA_JSON_KEY_UPDATE_TIME] = std::to_string(preset.updated_time);
|
||||
key_values[BBL_JSON_KEY_TYPE] = Preset::get_iot_type_string(preset.type);
|
||||
return 0;
|
||||
}
|
||||
@@ -1802,8 +1802,8 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
||||
|
||||
//update_time
|
||||
long long cloud_update_time = 0;
|
||||
if (preset_values.find(BBL_JSON_KEY_UPDATE_TIME) != preset_values.end()) {
|
||||
cloud_update_time = std::atoll(preset_values[BBL_JSON_KEY_UPDATE_TIME].c_str());
|
||||
if (preset_values.find(ORCA_JSON_KEY_UPDATE_TIME) != preset_values.end()) {
|
||||
cloud_update_time = std::atoll(preset_values[ORCA_JSON_KEY_UPDATE_TIME].c_str());
|
||||
}
|
||||
|
||||
//user_id
|
||||
@@ -2764,7 +2764,7 @@ size_t PresetCollection::first_visible_idx() const
|
||||
size_t first_visible = -1;
|
||||
size_t idx = m_default_suppressed ? m_num_default_presets : 0;
|
||||
for (; idx < m_presets.size(); ++ idx)
|
||||
if (m_presets[idx].is_visible && m_presets[idx].get_printer_id() == "BBL") {
|
||||
if (m_presets[idx].is_visible && m_presets[idx].get_printer_id() == PresetBundle::ORCA_FILAMENT_LIBRARY) {
|
||||
if (first_visible == -1)
|
||||
first_visible = idx;
|
||||
if (m_type != Preset::TYPE_FILAMENT)
|
||||
@@ -2785,6 +2785,46 @@ size_t PresetCollection::first_visible_idx() const
|
||||
return first_visible;
|
||||
}
|
||||
|
||||
size_t PresetCollection::first_visible_idx_by_type(const std::string& filament_type) const
|
||||
{
|
||||
size_t start = m_default_suppressed ? m_num_default_presets : 0;
|
||||
|
||||
// Find the first visible, compatible, system base preset whose filament_type matches target.
|
||||
auto find_by_type = [&](const std::string& target) -> size_t {
|
||||
for (size_t i = start; i < m_presets.size(); ++i) {
|
||||
const auto& p = m_presets[i];
|
||||
if (p.is_visible && p.is_compatible && p.is_system
|
||||
&& get_preset_base(p) == &p
|
||||
&& p.config.opt_string("filament_type", 0u) == target)
|
||||
return i;
|
||||
}
|
||||
return size_t(-1);
|
||||
};
|
||||
|
||||
// 1. Exact filament_type match
|
||||
size_t idx = find_by_type(filament_type);
|
||||
if (idx != size_t(-1))
|
||||
return idx;
|
||||
|
||||
// 2. Base type fallback: strip modifier after first space
|
||||
// e.g. "PLA High Speed" -> "PLA"
|
||||
// Dash-separated types like "PA-CF", "PET-CF" are distinct materials, not modifiers.
|
||||
auto sep = filament_type.find(' ');
|
||||
if (sep != std::string::npos) {
|
||||
idx = find_by_type(filament_type.substr(0, sep));
|
||||
if (idx != size_t(-1))
|
||||
return idx;
|
||||
}
|
||||
|
||||
// 3. Any visible preset
|
||||
return first_visible_idx();
|
||||
}
|
||||
|
||||
std::string PresetCollection::filament_id_by_type(const std::string& filament_type) const
|
||||
{
|
||||
return preset(first_visible_idx_by_type(filament_type)).filament_id;
|
||||
}
|
||||
|
||||
std::vector<std::string> PresetCollection::diameters_of_selected_printer()
|
||||
{
|
||||
std::set<std::string> diameters;
|
||||
@@ -3397,6 +3437,7 @@ static std::vector<std::string> s_PhysicalPrinter_opts {
|
||||
"printer_technology",
|
||||
"bbl_use_printhost",
|
||||
"host_type",
|
||||
"printer_agent",
|
||||
"print_host",
|
||||
"print_host_webui",
|
||||
"printhost_apikey",
|
||||
|
||||
@@ -53,7 +53,9 @@
|
||||
#define BBL_JSON_KEY_BASE_ID "base_id"
|
||||
#define BBL_JSON_KEY_USER_ID "user_id"
|
||||
#define BBL_JSON_KEY_FILAMENT_ID "filament_id"
|
||||
#define BBL_JSON_KEY_UPDATE_TIME "updated_time"
|
||||
#define UNKNOWN_FILAMENT_ID "__unknown__"
|
||||
#define ORCA_JSON_KEY_UPDATE_TIME "updated_time"
|
||||
#define ORCA_JSON_KEY_CREATED_TIME "created_time"
|
||||
#define BBL_JSON_KEY_INHERITS "inherits"
|
||||
#define BBL_JSON_KEY_INSTANTIATION "instantiation"
|
||||
#define BBL_JSON_KEY_NOZZLE_DIAMETER "nozzle_diameter"
|
||||
@@ -636,6 +638,11 @@ 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.
|
||||
size_t first_visible_idx_by_type(const std::string& filament_type) const;
|
||||
// Return the filament_id of the best-matching visible preset for the given filament type.
|
||||
std::string filament_id_by_type(const std::string& filament_type) const;
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
// If one of the prefered_alternates is compatible, select it.
|
||||
template<typename PreferedCondition> size_t first_compatible_idx(PreferedCondition prefered_condition) const
|
||||
|
||||
@@ -2208,8 +2208,14 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
|
||||
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
|
||||
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
|
||||
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
|
||||
|
||||
|
||||
filament_color->resize(n);
|
||||
filament_multi_color->resize(n);
|
||||
// Sync filament multi colour
|
||||
filament_multi_color->values.resize(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
filament_multi_color->values[i] = filament_color->values[i];
|
||||
}
|
||||
filament_color_type->resize(n);
|
||||
filament_map->values.resize(n, 1);
|
||||
ams_multi_color_filment.resize(n);
|
||||
@@ -2345,6 +2351,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
{
|
||||
bool valid{false};
|
||||
bool is_map{false};
|
||||
bool is_placeholder{false};
|
||||
std::string filament_color = "";
|
||||
std::string filament_color_type = "";
|
||||
std::string filament_preset = "";
|
||||
@@ -2362,7 +2369,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values;
|
||||
auto ams_id = ams.opt_string("ams_id", 0u);
|
||||
auto slot_id = ams.opt_string("slot_id", 0u);
|
||||
ams_infos.push_back({filament_id.empty() ? false : true,false, filament_color});
|
||||
auto is_placeholder = ams.has("filament_slot_placeholder") && ams.opt_bool("filament_slot_placeholder", 0u);
|
||||
ams_infos.push_back({filament_id.empty() ? false : true, false, is_placeholder, filament_color});
|
||||
AMSMapInfo temp = {ams_id, slot_id};
|
||||
ams_array_maps.push_back(temp);
|
||||
index++;
|
||||
@@ -2381,6 +2389,12 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
filament_multi_color.push_back(default_unknown_color);
|
||||
}
|
||||
ams_multi_color_filment.push_back(filament_multi_color);
|
||||
} else if (is_placeholder) {
|
||||
// Orca: push placeholders to keep index alignment with ams_infos
|
||||
ams_filament_presets.push_back("");
|
||||
ams_filament_colors.push_back("");
|
||||
ams_filament_color_types.push_back("");
|
||||
ams_multi_color_filment.push_back({});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -2399,11 +2413,41 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
if (iter == filaments.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
|
||||
if (!filament_type.empty()) {
|
||||
auto original_type = filament_type;
|
||||
filament_type = "Generic " + filament_type;
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) {
|
||||
return f.is_compatible && f.is_system
|
||||
&& boost::algorithm::starts_with(f.name, filament_type);
|
||||
});
|
||||
if (iter == filaments.end()) {
|
||||
// Similarity fallback: find a generic preset whose filament_type
|
||||
// appears as a whole word in the AMS type (e.g. "ASA" in "ASA Sparkle").
|
||||
auto upper_type = boost::to_upper_copy(original_type);
|
||||
auto contains_word = [](const std::string& haystack, const std::string& needle) {
|
||||
auto pos = haystack.find(needle);
|
||||
while (pos != std::string::npos) {
|
||||
bool start_ok = (pos == 0 || !std::isalnum(static_cast<unsigned char>(haystack[pos - 1])));
|
||||
bool end_ok = (pos + needle.size() >= haystack.size() ||
|
||||
!std::isalnum(static_cast<unsigned char>(haystack[pos + needle.size()])));
|
||||
if (start_ok && end_ok)
|
||||
return true;
|
||||
pos = haystack.find(needle, pos + 1);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// Find the longest-matching preset type to prefer e.g. "PA-CF" over "PA".
|
||||
size_t best_len = 0;
|
||||
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
|
||||
if (!it->is_compatible || !it->is_system || !boost::algorithm::starts_with(it->name, "Generic "))
|
||||
continue;
|
||||
auto preset_type = boost::to_upper_copy(it->config.opt_string("filament_type", 0u));
|
||||
if (preset_type.size() > best_len && contains_word(upper_type, preset_type)) {
|
||||
iter = it;
|
||||
best_len = preset_type.size();
|
||||
filament_type = "Generic " + it->config.opt_string("filament_type", 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (iter == filaments.end()) {
|
||||
// Prefer old selection
|
||||
@@ -2417,8 +2461,13 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
continue;
|
||||
}
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) {
|
||||
return f.is_compatible && f.is_system;
|
||||
return f.is_compatible && f.is_system
|
||||
&& boost::algorithm::starts_with(f.name, "Generic ");
|
||||
});
|
||||
if (iter == filaments.end())
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) {
|
||||
return f.is_compatible && f.is_system;
|
||||
});
|
||||
if (iter == filaments.end())
|
||||
continue;
|
||||
}
|
||||
@@ -2559,18 +2608,73 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
filament_map->values.resize(exist_filament_presets.size(), 1);
|
||||
}
|
||||
else {//overwrite;
|
||||
filament_color->values = ams_filament_colors;
|
||||
filament_color_type->values = ams_filament_color_types;
|
||||
this->filament_presets = ams_filament_presets;
|
||||
filament_map->values.resize(ams_filament_colors.size(), 1);
|
||||
bool has_placeholders = std::any_of(ams_infos.begin(), ams_infos.end(),
|
||||
[](const AmsInfo& a) { return a.is_placeholder; });
|
||||
if (has_placeholders) {
|
||||
// Orca: merge — keep existing filaments for empty slots
|
||||
auto exist_colors = filament_color->values;
|
||||
auto exist_color_types = filament_color_type->values;
|
||||
auto exist_presets = this->filament_presets;
|
||||
|
||||
size_t tray_count = ams_filament_presets.size();
|
||||
size_t total = std::max(tray_count, exist_presets.size());
|
||||
|
||||
std::vector<std::string> result_colors;
|
||||
std::vector<std::string> result_color_types;
|
||||
std::vector<std::string> result_presets;
|
||||
std::vector<std::vector<std::string>> result_multi_colors;
|
||||
|
||||
for (size_t i = 0; i < total; i++) {
|
||||
bool is_loaded = (i < ams_infos.size() && ams_infos[i].valid);
|
||||
|
||||
if (is_loaded) {
|
||||
// Loaded tray: use tray's filament data
|
||||
result_colors.push_back(ams_filament_colors[i]);
|
||||
result_color_types.push_back(ams_filament_color_types[i]);
|
||||
result_presets.push_back(ams_filament_presets[i]);
|
||||
result_multi_colors.push_back(
|
||||
i < ams_multi_color_filment.size() ? ams_multi_color_filment[i]
|
||||
: std::vector<std::string>{ams_filament_colors[i]});
|
||||
} else if (i < exist_presets.size()) {
|
||||
// Empty tray or beyond tray count: keep existing filament
|
||||
result_colors.push_back(exist_colors[i]);
|
||||
result_color_types.push_back(exist_color_types[i]);
|
||||
result_presets.push_back(exist_presets[i]);
|
||||
result_multi_colors.push_back({exist_colors[i]});
|
||||
} else {
|
||||
// New slot beyond existing count: prefer a generic filament preset
|
||||
auto it = std::find_if(filaments.begin(), filaments.end(), [](const Preset &f) {
|
||||
return f.is_compatible && f.is_system
|
||||
&& boost::algorithm::starts_with(f.name, "Generic ");
|
||||
});
|
||||
std::string fallback_name = (it != filaments.end()) ? it->name : filaments.first_visible().name;
|
||||
result_colors.push_back("#CECECE");
|
||||
result_color_types.push_back("1");
|
||||
result_presets.push_back(fallback_name);
|
||||
result_multi_colors.push_back({"#CECECE"});
|
||||
}
|
||||
}
|
||||
|
||||
filament_color->values = result_colors;
|
||||
filament_color_type->values = result_color_types;
|
||||
this->filament_presets = result_presets;
|
||||
ams_multi_color_filment = result_multi_colors;
|
||||
filament_map->values.resize(total, 1);
|
||||
} else {
|
||||
// BBL: existing wholesale replace
|
||||
filament_color->values = ams_filament_colors;
|
||||
filament_color_type->values = ams_filament_color_types;
|
||||
this->filament_presets = ams_filament_presets;
|
||||
filament_map->values.resize(ams_filament_colors.size(), 1);
|
||||
}
|
||||
|
||||
auto& print_config = this->prints.get_edited_preset().config;
|
||||
auto support_filament_opt = print_config.option<ConfigOptionInt>("support_filament");
|
||||
auto support_interface_filament_opt = print_config.option<ConfigOptionInt>("support_interface_filament");
|
||||
if (support_filament_opt->value > ams_filament_color_types.size())
|
||||
if (support_filament_opt->value > filament_color_type->values.size())
|
||||
support_filament_opt->value = 0;
|
||||
|
||||
if (support_interface_filament_opt->value > ams_filament_color_types.size())
|
||||
if (support_interface_filament_opt->value > filament_color_type->values.size())
|
||||
support_interface_filament_opt->value = 0;
|
||||
}
|
||||
// Update ams_multi_color_filment
|
||||
|
||||
@@ -740,6 +740,13 @@ void PrintConfigDef::init_common_params()
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("printer_agent", coString);
|
||||
def->label = L("Printer Agent");
|
||||
def->tooltip = L("Select the network agent implementation for printer communication.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("print_host", coString);
|
||||
def->label = L("Hostname, IP or URL");
|
||||
def->tooltip = L("Orca Slicer can upload G-code files to a printer host. This field should contain "
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <cassert>
|
||||
#include <ctime>
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <map>
|
||||
@@ -230,5 +232,76 @@ time_t str2time(const std::string &str, TimeZone zone, TimeFormat fmt)
|
||||
return str2time(ss, zone, fmtstr.c_str());
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Millisecond timestamps for cloud sync protocol
|
||||
|
||||
std::string millis_to_iso8601(long long unix_millis)
|
||||
{
|
||||
time_t seconds = static_cast<time_t>(unix_millis / 1000);
|
||||
int millis = static_cast<int>(unix_millis % 1000);
|
||||
|
||||
std::tm tms = {};
|
||||
_gmtime_r(&seconds, &tms);
|
||||
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
|
||||
tms.tm_year + 1900,
|
||||
tms.tm_mon + 1,
|
||||
tms.tm_mday,
|
||||
tms.tm_hour,
|
||||
tms.tm_min,
|
||||
tms.tm_sec,
|
||||
millis);
|
||||
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
long long iso8601_to_millis(const std::string& iso_time)
|
||||
{
|
||||
if (iso_time.empty()) return -1;
|
||||
|
||||
int y, M, d, h, m, s, ms = 0;
|
||||
|
||||
// Try parsing with milliseconds: "2025-11-28T14:30:00.123Z"
|
||||
int parsed = sscanf(iso_time.c_str(), "%d-%d-%dT%d:%d:%d.%dZ",
|
||||
&y, &M, &d, &h, &m, &s, &ms);
|
||||
|
||||
if (parsed < 6) {
|
||||
// Try without milliseconds: "2025-11-28T14:30:00Z"
|
||||
parsed = sscanf(iso_time.c_str(), "%d-%d-%dT%d:%d:%dZ",
|
||||
&y, &M, &d, &h, &m, &s);
|
||||
ms = 0;
|
||||
}
|
||||
|
||||
if (parsed < 6) return -1;
|
||||
|
||||
// Normalize milliseconds (handle .1, .12, .123, .1234, etc.)
|
||||
if (parsed >= 7) {
|
||||
// Count digits in the fractional part to normalize
|
||||
const char* dot = strchr(iso_time.c_str(), '.');
|
||||
if (dot) {
|
||||
int digits = 0;
|
||||
for (const char* p = dot + 1; *p && *p != 'Z' && std::isdigit(*p); ++p)
|
||||
digits++;
|
||||
// Normalize to 3 digits (milliseconds)
|
||||
while (digits < 3) { ms *= 10; digits++; }
|
||||
while (digits > 3) { ms /= 10; digits--; }
|
||||
}
|
||||
}
|
||||
|
||||
std::tm tms = {};
|
||||
tms.tm_year = y - 1900;
|
||||
tms.tm_mon = M - 1;
|
||||
tms.tm_mday = d;
|
||||
tms.tm_hour = h;
|
||||
tms.tm_min = m;
|
||||
tms.tm_sec = s;
|
||||
|
||||
time_t seconds = _timegm(&tms);
|
||||
if (seconds == time_t(-1)) return -1;
|
||||
|
||||
return static_cast<long long>(seconds) * 1000 + ms;
|
||||
}
|
||||
|
||||
}; // namespace Utils
|
||||
}; // namespace Slic3r
|
||||
|
||||
@@ -63,6 +63,15 @@ inline time_t parse_iso_utc_timestamp(const std::string &str)
|
||||
return str2time(str, TimeZone::utc, TimeFormat::iso8601Z);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Millisecond timestamps for cloud sync protocol
|
||||
// Format: "2025-11-28T14:30:00.123Z" (ISO 8601 with milliseconds)
|
||||
|
||||
// Lossless conversion: Unix milliseconds <-> ISO 8601
|
||||
// Format: "YYYY-MM-DDTHH:MM:SS.sssZ" (always 3 decimal places for milliseconds)
|
||||
std::string millis_to_iso8601(long long unix_millis);
|
||||
long long iso8601_to_millis(const std::string& iso_time); // Returns -1 on parse error
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace Utils
|
||||
|
||||
@@ -598,6 +598,26 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/MKS.hpp
|
||||
Utils/NetworkAgent.cpp
|
||||
Utils/NetworkAgent.hpp
|
||||
Utils/NetworkAgentFactory.hpp
|
||||
Utils/NetworkAgentFactory.cpp
|
||||
Utils/ICloudServiceAgent.hpp
|
||||
Utils/IPrinterAgent.hpp
|
||||
Utils/OrcaCloudServiceAgent.cpp
|
||||
Utils/OrcaCloudServiceAgent.hpp
|
||||
Utils/OrcaPrinterAgent.cpp
|
||||
Utils/OrcaPrinterAgent.hpp
|
||||
Utils/QidiPrinterAgent.cpp
|
||||
Utils/QidiPrinterAgent.hpp
|
||||
Utils/SnapmakerPrinterAgent.cpp
|
||||
Utils/SnapmakerPrinterAgent.hpp
|
||||
Utils/MoonrakerPrinterAgent.cpp
|
||||
Utils/MoonrakerPrinterAgent.hpp
|
||||
Utils/BBLCloudServiceAgent.cpp
|
||||
Utils/BBLCloudServiceAgent.hpp
|
||||
Utils/BBLPrinterAgent.cpp
|
||||
Utils/BBLPrinterAgent.hpp
|
||||
Utils/BBLNetworkPlugin.cpp
|
||||
Utils/BBLNetworkPlugin.hpp
|
||||
Utils/Obico.cpp
|
||||
Utils/Obico.hpp
|
||||
Utils/OctoPrint.cpp
|
||||
|
||||
@@ -869,7 +869,7 @@ 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_nickanme());
|
||||
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname());
|
||||
m_user_name->SetLabelText(username_text);
|
||||
|
||||
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
|
||||
|
||||
@@ -612,6 +612,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_tray->remain = -1;
|
||||
}
|
||||
if (tray_it->contains("tray_slot_placeholder")) {
|
||||
curr_tray->is_slot_placeholder = true;
|
||||
}
|
||||
int ams_id_int = 0;
|
||||
int tray_id_int = 0;
|
||||
try
|
||||
|
||||
@@ -53,6 +53,7 @@ public:
|
||||
wxColour wx_color;
|
||||
bool is_bbl;
|
||||
bool is_exists = false;
|
||||
bool is_slot_placeholder = false; // Orca: True for empty tray slots from pull-mode agents
|
||||
int hold_count = 0;
|
||||
int remain = 0; // filament remain: 0 ~ 100
|
||||
|
||||
|
||||
@@ -518,7 +518,7 @@ namespace Slic3r
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
|
||||
#else
|
||||
it->second->connect(it->second->local_use_ssl_for_mqtt);
|
||||
it->second->connect(it->second->local_use_ssl);
|
||||
#endif
|
||||
it->second->set_lan_mode_connection_state(true);
|
||||
}
|
||||
@@ -542,7 +542,7 @@ namespace Slic3r
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
|
||||
#else
|
||||
it->second->connect(it->second->local_use_ssl_for_mqtt);
|
||||
it->second->connect(it->second->local_use_ssl);
|
||||
#endif
|
||||
it->second->set_lan_mode_connection_state(true);
|
||||
}
|
||||
@@ -858,7 +858,10 @@ namespace Slic3r
|
||||
{
|
||||
if (MachineObject* obj_ = get_selected_machine()) {
|
||||
GUI::wxGetApp().sidebar().update_sync_status(obj_);
|
||||
GUI::wxGetApp().sidebar().load_ams_list(obj_);
|
||||
if(m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription)
|
||||
{
|
||||
GUI::wxGetApp().sidebar().load_ams_list(obj_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -363,6 +363,30 @@ std::string MachineObject::get_ftp_folder()
|
||||
return DevPrinterConfigUtil::get_ftp_folder(printer_type);
|
||||
}
|
||||
|
||||
std::string MachineObject::dev_id_from_address(const std::string& host, const std::string& port)
|
||||
{
|
||||
std::string result = host;
|
||||
// Normalize host: strip protocol and path
|
||||
if (result.find("http://") == 0)
|
||||
result = result.substr(7);
|
||||
else if (result.find("https://") == 0)
|
||||
result = result.substr(8);
|
||||
auto slash = result.find('/');
|
||||
if (slash != std::string::npos)
|
||||
result = result.substr(0, slash);
|
||||
|
||||
// Build full address (host:port)
|
||||
if (!port.empty()) {
|
||||
// Strip inline port if present (port comes from printhost_port)
|
||||
auto colon = result.find(':');
|
||||
if (colon != std::string::npos)
|
||||
result = result.substr(0, colon);
|
||||
|
||||
result += ":" + port;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MachineObject::HasRecentCloudMessage()
|
||||
{
|
||||
auto curr_time = std::chrono::system_clock::now();
|
||||
|
||||
@@ -164,7 +164,11 @@ public:
|
||||
std::string get_dev_id() const { return dev_id; }
|
||||
void set_dev_id(std::string val) { dev_id = val; }
|
||||
|
||||
bool local_use_ssl_for_mqtt { true };
|
||||
// Generate consistent dev_id from host address and optional port
|
||||
// Returns "host:port" or "host" if port is empty
|
||||
static std::string dev_id_from_address(const std::string& host, const std::string& port = "");
|
||||
|
||||
bool local_use_ssl { true };
|
||||
bool local_use_ssl_for_ftp { true };
|
||||
std::string get_ftp_folder();
|
||||
|
||||
@@ -255,6 +259,8 @@ public:
|
||||
long tray_read_done_bits = 0;
|
||||
long tray_reading_bits = 0;
|
||||
bool ams_air_print_status { false };
|
||||
/** Whether this printer supports virtual trays (external/manual filament loading).
|
||||
* When true, vt_slot data is used by build_filament_ams_list() to include external filaments. */
|
||||
bool ams_support_virtual_tray { true };
|
||||
time_t ams_user_setting_start = 0;
|
||||
time_t ams_switch_filament_start = 0;
|
||||
@@ -856,7 +862,16 @@ public:
|
||||
bool is_enable_np{ false };
|
||||
bool is_enable_ams_np{ false };
|
||||
|
||||
/*vi slot data*/
|
||||
/**
|
||||
* Virtual Tray (vt_slot) - External/manual filament loading slots.
|
||||
*
|
||||
* Data Flow: Populated from printer JSON via parse_vt_tray() during MachineObject::parse_json().
|
||||
* Used by: Sidebar::build_filament_ams_list() when ams_support_virtual_tray is true.
|
||||
*
|
||||
* Virtual trays represent filament that is manually loaded into the extruder
|
||||
* rather than fed through an AMS unit. This supports printers without AMS
|
||||
* or scenarios where users want to bypass the AMS.
|
||||
*/
|
||||
std::vector<DevAmsTray> vt_slot;
|
||||
DevAmsTray parse_vt_tray(json vtray);
|
||||
|
||||
|
||||
+294
-122
@@ -119,6 +119,10 @@
|
||||
#include "ModelMall.hpp"
|
||||
#include "HintNotification.hpp"
|
||||
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
//#ifdef WIN32
|
||||
//#include "BaseException.h"
|
||||
//#endif
|
||||
@@ -1176,9 +1180,9 @@ std::string GUI_App::get_plugin_url(std::string name, std::string country_code)
|
||||
curr_version = BAMBU_NETWORK_AGENT_VERSION_LEGACY;
|
||||
} else if (name == "plugins" && app_config) {
|
||||
std::string user_version = app_config->get_network_plugin_version();
|
||||
curr_version = user_version.empty() ? BBL::get_latest_network_version() : user_version;
|
||||
curr_version = user_version.empty() ? get_latest_network_version() : user_version;
|
||||
} else {
|
||||
curr_version = BBL::get_latest_network_version();
|
||||
curr_version = get_latest_network_version();
|
||||
}
|
||||
|
||||
std::string using_version = curr_version.substr(0, 9) + "00";
|
||||
@@ -1519,7 +1523,7 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
if (name == "plugins") {
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
if (config_version.empty()) {
|
||||
config_version = BBL::get_latest_network_version();
|
||||
config_version = get_latest_network_version();
|
||||
BOOST_LOG_TRIVIAL(info) << "[install_plugin] config_version was empty, using latest: " << config_version;
|
||||
app_config->set_network_plugin_version(config_version);
|
||||
GUI::wxGetApp().CallAfter([this] {
|
||||
@@ -1814,7 +1818,7 @@ bool GUI_App::hot_reload_network_plugin()
|
||||
|
||||
std::string GUI_App::get_latest_network_version() const
|
||||
{
|
||||
return BBL::get_latest_network_version();
|
||||
return Slic3r::get_latest_network_version();
|
||||
}
|
||||
|
||||
bool GUI_App::has_network_update_available() const
|
||||
@@ -1919,9 +1923,9 @@ bool GUI_App::check_networking_version()
|
||||
studio_ver = BAMBU_NETWORK_AGENT_VERSION_LEGACY;
|
||||
} else if (app_config) {
|
||||
std::string user_version = app_config->get_network_plugin_version();
|
||||
studio_ver = user_version.empty() ? BBL::get_latest_network_version() : user_version;
|
||||
studio_ver = user_version.empty() ? get_latest_network_version() : user_version;
|
||||
} else {
|
||||
studio_ver = BBL::get_latest_network_version();
|
||||
studio_ver = get_latest_network_version();
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "check_networking_version: network_ver=" << network_ver << ", expected=" << studio_ver;
|
||||
@@ -2197,7 +2201,9 @@ void GUI_App::init_networking_callbacks()
|
||||
|
||||
if (MachineObject* obj = m_device_manager->get_my_machine(dev_id)) {
|
||||
obj->parse_json("lan", msg);
|
||||
if (this->m_device_manager->get_selected_machine() == obj) {
|
||||
// Orca: skip it if it doesn't support subscription based filament sync
|
||||
if (this->m_device_manager->get_selected_machine() == obj &&
|
||||
m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription) {
|
||||
GUI::wxGetApp().sidebar().load_ams_list(obj);
|
||||
}
|
||||
}
|
||||
@@ -2233,6 +2239,8 @@ GUI_App::~GUI_App()
|
||||
}
|
||||
|
||||
StaticBambuLib::release();
|
||||
BBLNetworkPlugin::shutdown();
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
|
||||
}
|
||||
@@ -2583,6 +2591,11 @@ int GUI_App::OnExit()
|
||||
m_user_manager = nullptr;
|
||||
}
|
||||
|
||||
// Clear the printer agent cache before destroying the NetworkAgent.
|
||||
// This disconnects all cached agents and releases their shared_ptrs,
|
||||
// ensuring clean thread shutdown before the agent is deleted.
|
||||
NetworkAgentFactory::clear_printer_agent_cache();
|
||||
|
||||
if (m_agent) {
|
||||
// BBS avoid a crash on mac platform
|
||||
#ifdef __WINDOWS__
|
||||
@@ -3296,126 +3309,127 @@ void GUI_App::copy_network_if_available()
|
||||
|
||||
bool GUI_App::on_init_network(bool try_backup)
|
||||
{
|
||||
bool create_network_agent = false;
|
||||
auto should_load_networking_plugin = app_config->get_bool("installed_networking");
|
||||
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
|
||||
if(!should_load_networking_plugin) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Don't load plugin as installed_networking is false";
|
||||
} else {
|
||||
if (config_version.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
|
||||
m_networking_need_update = true;
|
||||
if (should_load_networking_plugin) {
|
||||
if (config_version.empty()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download";
|
||||
m_networking_need_update = true;
|
||||
|
||||
if (!m_device_manager)
|
||||
m_device_manager = new Slic3r::DeviceManager();
|
||||
if (!m_user_manager)
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
if (!m_device_manager)
|
||||
m_device_manager = new Slic3r::DeviceManager();
|
||||
if (!m_user_manager)
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
|
||||
return false;
|
||||
}
|
||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
__retry:
|
||||
if (!load_agent_dll) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
|
||||
|
||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
std::string config_base = BBL::extract_base_version(config_version);
|
||||
if (config_base != loaded_version) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version << " to loaded " << loaded_version;
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, loaded_version);
|
||||
app_config->save();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (check_networking_version()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
||||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
if (!bambu_source) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
||||
m_networking_compatible = false;
|
||||
int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version);
|
||||
__retry:
|
||||
if (!load_agent_dll) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok";
|
||||
|
||||
std::string loaded_version = Slic3r::NetworkAgent::get_version();
|
||||
if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") {
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
std::string config_base = extract_base_version(config_version);
|
||||
if (config_base != loaded_version) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version << " to loaded "
|
||||
<< loaded_version;
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, loaded_version);
|
||||
app_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (check_networking_version()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
|
||||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
if (!bambu_source) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
||||
m_networking_compatible = false;
|
||||
if (should_load_networking_plugin) {
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (try_backup) {
|
||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version);
|
||||
try_backup = false;
|
||||
goto __retry;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, version dismatch, need upload network module";
|
||||
if (should_load_networking_plugin) {
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
create_network_agent = true;
|
||||
} else {
|
||||
if (try_backup) {
|
||||
int result = Slic3r::NetworkAgent::unload_network_module();
|
||||
BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result;
|
||||
load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version);
|
||||
try_backup = false;
|
||||
goto __retry;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, version dismatch, need upload network module";
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
|
||||
if (should_load_networking_plugin) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", create network agent...");
|
||||
//std::string data_dir = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
|
||||
std::string data_directory = data_dir();
|
||||
|
||||
// Register all printer agents before creating the network agent
|
||||
Slic3r::NetworkAgentFactory::register_all_agents();
|
||||
|
||||
// m_agent = new Slic3r::NetworkAgent(data_directory);
|
||||
std::unique_ptr<Slic3r::NetworkAgent> agent_ptr = Slic3r::create_agent_from_config(data_directory, app_config);
|
||||
m_agent = agent_ptr.release();
|
||||
|
||||
if (!m_device_manager)
|
||||
m_device_manager = new Slic3r::DeviceManager(m_agent);
|
||||
else
|
||||
m_device_manager->set_agent(m_agent);
|
||||
|
||||
if (!m_user_manager)
|
||||
m_user_manager = new Slic3r::UserManager(m_agent);
|
||||
else
|
||||
m_user_manager->set_agent(m_agent);
|
||||
|
||||
if (this->is_enable_multi_machine()) {
|
||||
if (!m_task_manager) {
|
||||
m_task_manager = new Slic3r::TaskManager(m_agent);
|
||||
m_task_manager->start();
|
||||
}
|
||||
|
||||
m_device_manager->EnableMultiMachine(true);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll failed";
|
||||
if (should_load_networking_plugin) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, need upload network module";
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
}
|
||||
m_device_manager->EnableMultiMachine(false);
|
||||
}
|
||||
|
||||
if (create_network_agent) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", create network agent...");
|
||||
//std::string data_dir = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
|
||||
std::string data_directory = data_dir();
|
||||
|
||||
m_agent = new Slic3r::NetworkAgent(data_directory);
|
||||
|
||||
if (!m_device_manager)
|
||||
m_device_manager = new Slic3r::DeviceManager(m_agent);
|
||||
else
|
||||
m_device_manager->set_agent(m_agent);
|
||||
|
||||
if (!m_user_manager)
|
||||
m_user_manager = new Slic3r::UserManager(m_agent);
|
||||
else
|
||||
m_user_manager->set_agent(m_agent);
|
||||
|
||||
if (this->is_enable_multi_machine()) {
|
||||
if (!m_task_manager) {
|
||||
m_task_manager = new Slic3r::TaskManager(m_agent);
|
||||
m_task_manager->start();
|
||||
}
|
||||
|
||||
m_device_manager->EnableMultiMachine(true);
|
||||
} else {
|
||||
m_device_manager->EnableMultiMachine(false);
|
||||
}
|
||||
|
||||
//BBS set config dir
|
||||
if (m_agent) {
|
||||
m_agent->set_config_dir(data_directory);
|
||||
}
|
||||
//BBS start http log
|
||||
if (m_agent) {
|
||||
m_agent->init_log();
|
||||
}
|
||||
|
||||
//BBS set cert dir
|
||||
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();
|
||||
m_agent->set_country_code(country_code);
|
||||
m_agent->start();
|
||||
}
|
||||
//BBS set config dir
|
||||
if (m_agent) {
|
||||
m_agent->set_config_dir(data_directory);
|
||||
}
|
||||
else {
|
||||
//BBS start http log
|
||||
if (m_agent) {
|
||||
m_agent->init_log();
|
||||
}
|
||||
|
||||
//BBS set cert dir
|
||||
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();
|
||||
m_agent->set_country_code(country_code);
|
||||
m_agent->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;
|
||||
|
||||
@@ -3426,7 +3440,7 @@ __retry:
|
||||
m_user_manager = new Slic3r::UserManager();
|
||||
}
|
||||
|
||||
if (create_network_agent && m_networking_compatible && !NetworkAgent::use_legacy_network) {
|
||||
if (should_load_networking_plugin && m_networking_compatible && !NetworkAgent::use_legacy_network) {
|
||||
app_config->clear_remind_network_update_later();
|
||||
|
||||
if (has_network_update_available()) {
|
||||
@@ -3460,6 +3474,139 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
|
||||
));
|
||||
}
|
||||
|
||||
void GUI_App::switch_printer_agent()
|
||||
{
|
||||
if (!m_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no agent exists";
|
||||
return;
|
||||
}
|
||||
|
||||
// Read printer_agent from config, falling back to default
|
||||
std::string effective_agent_id = ORCA_PRINTER_AGENT_ID;
|
||||
if (preset_bundle->is_bbl_vendor()) {
|
||||
effective_agent_id = BBL_PRINTER_AGENT_ID;
|
||||
} else {
|
||||
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printer_agent")) {
|
||||
const std::string& value = config.option<ConfigOptionString>("printer_agent")->value;
|
||||
if (!value.empty())
|
||||
effective_agent_id = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if agent is registered
|
||||
if (!NetworkAgentFactory::is_printer_agent_registered(effective_agent_id)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": unregistered agent ID '" << effective_agent_id
|
||||
<< "', keeping current agent";
|
||||
// Keep current agent, don't switch
|
||||
return;
|
||||
}
|
||||
|
||||
std::string current_agent_id;
|
||||
if (m_agent->get_printer_agent())
|
||||
current_agent_id = m_agent->get_printer_agent()->get_agent_info().id;
|
||||
|
||||
if (current_agent_id != effective_agent_id) {
|
||||
std::string log_dir = data_dir();
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent();
|
||||
|
||||
// Create new printer agent via registry
|
||||
std::shared_ptr<IPrinterAgent> new_printer_agent =
|
||||
NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir);
|
||||
|
||||
if (!new_printer_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent";
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the agent
|
||||
m_agent->set_printer_agent(new_printer_agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
// Auto-switch MachineObject
|
||||
select_machine(effective_agent_id);
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::select_machine(const std::string& agent_id)
|
||||
{
|
||||
// Skip for BBL agent for now - uses its own device discovery/selection
|
||||
// Orca todo: revisit in future if we want to support auto-switching for BBL printers
|
||||
if (agent_id == BBL_PRINTER_AGENT_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_device_manager || !preset_bundle) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no device manager or preset bundle";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get config source (preset or physical printer)
|
||||
const auto& preset = preset_bundle->printers.get_edited_preset();
|
||||
const DynamicPrintConfig* host_cfg = &preset.config;
|
||||
|
||||
std::string print_host = host_cfg->opt_string("print_host");
|
||||
if (print_host.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string port = host_cfg->opt_string("printhost_port");
|
||||
|
||||
// Generate dev_id from host and port
|
||||
std::string dev_id = MachineObject::dev_id_from_address(print_host, port);
|
||||
|
||||
// Check if already exists by dev_id
|
||||
MachineObject* existing = m_device_manager->get_local_machine(dev_id);
|
||||
|
||||
// If not found by dev_id, search by full_addr
|
||||
if (!existing) {
|
||||
auto local_machines = m_device_manager->get_local_machinelist();
|
||||
for (auto& [id, machine] : local_machines) {
|
||||
if (machine && machine->get_dev_ip() == dev_id) {
|
||||
existing = machine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If machine doesn't exist, create it first
|
||||
if (!existing) {
|
||||
BBLocalMachine machine;
|
||||
machine.dev_id = dev_id;
|
||||
// We use dev_id as dev_ip to store the address (host:port)
|
||||
machine.dev_ip = dev_id;
|
||||
machine.dev_name = dev_id;
|
||||
machine.printer_type = preset.config.opt_string("printer_model");
|
||||
auto access_code = preset.config.opt_string("printhost_apikey");
|
||||
// Orca expect non empty access code
|
||||
if (access_code.empty()) {
|
||||
access_code = "88888888";
|
||||
}
|
||||
|
||||
existing = m_device_manager->insert_local_device(
|
||||
machine, "lan", "free", "", access_code);
|
||||
|
||||
if (!existing) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create machine dev_id=" << dev_id;
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created new machine dev_id=" << dev_id;
|
||||
}
|
||||
existing->local_use_ssl = boost::istarts_with(print_host, "https://");
|
||||
|
||||
// Use MonitorPanel::select_machine() to trigger full selection flow
|
||||
// This reuses existing logic for machine switching (UI updates, callbacks, etc.)
|
||||
if (mainframe && mainframe->m_monitor) {
|
||||
mainframe->m_monitor->select_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": triggered select_machine for dev_id=" << dev_id;
|
||||
} else {
|
||||
// Fallback if MonitorPanel not available
|
||||
m_device_manager->set_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fallback set_selected_machine dev_id=" << dev_id;
|
||||
}
|
||||
}
|
||||
|
||||
bool GUI_App::dark_mode()
|
||||
{
|
||||
#ifdef SUPPORT_DARK_MODE
|
||||
@@ -4283,10 +4430,14 @@ void GUI_App::get_login_info()
|
||||
GUI::wxGetApp().run_script(strJS);
|
||||
}
|
||||
else {
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
mainframe->m_webview->SetLoginPanelVisibility(true);
|
||||
}
|
||||
@@ -5635,7 +5786,7 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
if (!new_setting_id.empty()) {
|
||||
setting_id = new_setting_id;
|
||||
result = 0;
|
||||
auto update_time_str = values_map[BBL_JSON_KEY_UPDATE_TIME];
|
||||
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());
|
||||
}
|
||||
@@ -5664,7 +5815,7 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
if (!new_setting_id.empty()) {
|
||||
setting_id = new_setting_id;
|
||||
result = 0;
|
||||
auto update_time_str = values_map[BBL_JSON_KEY_UPDATE_TIME];
|
||||
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());
|
||||
} else {
|
||||
@@ -5690,16 +5841,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[BBL_JSON_KEY_UPDATE_TIME];
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5752,6 +5903,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
return;
|
||||
|
||||
if (!m_agent || !m_agent->is_user_login()) return;
|
||||
if(!m_agent->get_cloud_agent())
|
||||
return;
|
||||
|
||||
// has already start sync
|
||||
if (m_user_sync_token) return;
|
||||
@@ -5801,7 +5954,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
auto type = info[BBL_JSON_KEY_TYPE];
|
||||
auto name = info[BBL_JSON_KEY_NAME];
|
||||
auto setting_id = info[BBL_JSON_KEY_SETTING_ID];
|
||||
auto update_time_str = info[BBL_JSON_KEY_UPDATE_TIME];
|
||||
auto update_time_str = info[ORCA_JSON_KEY_UPDATE_TIME];
|
||||
long long update_time = 0;
|
||||
if (!update_time_str.empty())
|
||||
update_time = std::atoll(update_time_str.c_str());
|
||||
@@ -5911,6 +6064,25 @@ void GUI_App::start_http_server()
|
||||
if (!m_http_server.is_started())
|
||||
m_http_server.start();
|
||||
}
|
||||
|
||||
void GUI_App::start_http_server(int port)
|
||||
{
|
||||
if (port <= 0) {
|
||||
start_http_server();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_http_server.is_started()) {
|
||||
if (m_http_server.get_port() == static_cast<boost::asio::ip::port_type>(port)) {
|
||||
return;
|
||||
}
|
||||
m_http_server.stop();
|
||||
}
|
||||
|
||||
m_http_server.set_port(static_cast<boost::asio::ip::port_type>(port));
|
||||
m_http_server.start();
|
||||
}
|
||||
|
||||
void GUI_App::stop_http_server()
|
||||
{
|
||||
m_http_server.stop();
|
||||
|
||||
@@ -340,6 +340,10 @@ public:
|
||||
Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
|
||||
HMSQuery* get_hms_query() { return hms_query; }
|
||||
NetworkAgent* getAgent() { return m_agent; }
|
||||
|
||||
// Dynamic printer agent switching
|
||||
void switch_printer_agent();
|
||||
|
||||
FilamentColorCodeQuery* get_filament_color_code_query();
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
@@ -501,6 +505,7 @@ public:
|
||||
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);
|
||||
void stop_http_server();
|
||||
void switch_staff_pick(bool on);
|
||||
|
||||
@@ -720,6 +725,9 @@ private:
|
||||
bool config_wizard_startup();
|
||||
void check_updates(const bool verbose);
|
||||
|
||||
// select or add MachineObject
|
||||
void select_machine(const std::string& agent_id);
|
||||
|
||||
bool m_init_app_config_from_older { false };
|
||||
bool m_datadir_redefined { false };
|
||||
std::string m_older_data_dir_path;
|
||||
|
||||
@@ -182,6 +182,42 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
|
||||
{
|
||||
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");
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (!agent) {
|
||||
return std::make_shared<ResponseNotFound>();
|
||||
}
|
||||
|
||||
json payload;
|
||||
payload["command"] = "user_login";
|
||||
payload["data"]["code"] = auth_code;
|
||||
payload["data"]["state"] = state;
|
||||
|
||||
agent->change_user(payload.dump());
|
||||
const bool login_ok = agent->is_user_login();
|
||||
if (login_ok) {
|
||||
wxGetApp().request_user_login(1);
|
||||
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
|
||||
}
|
||||
|
||||
const std::string title = login_ok ? "Authentication complete" : "Authentication failed";
|
||||
const std::string message = login_ok
|
||||
? "You can return to OrcaSlicer. This window will close automatically."
|
||||
: "Something went wrong. Please return to OrcaSlicer and try again.";
|
||||
const std::string html =
|
||||
"<html><head><meta charset=\"utf-8\">"
|
||||
"<style>body{font-family:Arial,sans-serif;background:#f7f7f7;color:#222;margin:32px;}"
|
||||
"a.button{display:inline-block;padding:10px 16px;margin-top:12px;background:#0f8bff;color:#fff;text-decoration:none;border-radius:6px;}"
|
||||
"</style></head><body><div class=\"container\">"
|
||||
"<h2>" + title + "</h2>"
|
||||
"<p>" + message + "</p>"
|
||||
"<script>setTimeout(function(){try{window.close();}catch(e){}},1500);</script>"
|
||||
"</div></body></html>";
|
||||
return std::make_shared<ResponseHtml>(html);
|
||||
}
|
||||
|
||||
if (boost::contains(url, "access_token")) {
|
||||
std::string redirect_url = url_get_param(url, "redirect_url");
|
||||
std::string access_token = url_get_param(url, "access_token");
|
||||
@@ -249,7 +285,17 @@ void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut)
|
||||
|
||||
void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut)
|
||||
{
|
||||
const std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
|
||||
const std::string sHTML =
|
||||
"<html><head><meta charset=\"utf-8\">"
|
||||
"<meta http-equiv=\"refresh\" content=\"0;url=" + location_str + "\">"
|
||||
"<style>body{font-family:Arial,sans-serif;background:#f7f7f7;color:#222;margin:32px;}"
|
||||
"a.button{display:inline-block;padding:10px 16px;margin-top:12px;background:#0f8bff;color:#fff;text-decoration:none;border-radius:6px;}"
|
||||
"</style></head><body><div class=\"container\">"
|
||||
"<h2>Authentication complete</h2>"
|
||||
"<p>You can return to OrcaSlicer. If your browser does not redirect automatically, use the button below.</p>"
|
||||
"<a class=\"button\" href=\"" + location_str + "\">Continue</a>"
|
||||
"<script>setTimeout(function(){try{window.close();}catch(e){}},1500);</script>"
|
||||
"</div></body></html>";
|
||||
ssOut << "HTTP/1.1 302 Found" << std::endl;
|
||||
ssOut << "Location: " << location_str << std::endl;
|
||||
ssOut << "content-type: text/html" << std::endl;
|
||||
@@ -258,5 +304,14 @@ void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut)
|
||||
ssOut << sHTML;
|
||||
}
|
||||
|
||||
void HttpServer::ResponseHtml::write_response(std::stringstream& ssOut)
|
||||
{
|
||||
ssOut << "HTTP/1.1 200 OK" << std::endl;
|
||||
ssOut << "content-type: text/html" << std::endl;
|
||||
ssOut << "content-length: " << html.length() << std::endl;
|
||||
ssOut << std::endl;
|
||||
ssOut << html;
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} //Slic3r
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#define LOCALHOST_PORT 13618
|
||||
#define LOCALHOST_URL "http://localhost:"
|
||||
@@ -98,6 +99,16 @@ public:
|
||||
void write_response(std::stringstream& ssOut) override;
|
||||
};
|
||||
|
||||
class ResponseHtml : public Response
|
||||
{
|
||||
const std::string html;
|
||||
|
||||
public:
|
||||
explicit ResponseHtml(std::string html) : html(std::move(html)) {}
|
||||
~ResponseHtml() override = default;
|
||||
void write_response(std::stringstream& ssOut) override;
|
||||
};
|
||||
|
||||
HttpServer(boost::asio::ip::port_type port = LOCALHOST_PORT);
|
||||
|
||||
boost::thread m_http_server_thread;
|
||||
@@ -106,6 +117,8 @@ public:
|
||||
bool is_started() { return start_http_server; }
|
||||
void start();
|
||||
void stop();
|
||||
void set_port(boost::asio::ip::port_type new_port) { port = new_port; }
|
||||
boost::asio::ip::port_type get_port() const { return port; }
|
||||
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);
|
||||
|
||||
@@ -66,22 +66,22 @@ void BindJob::process(Ctl &ctl)
|
||||
result_code = code;
|
||||
result_info = info;
|
||||
|
||||
if (stage == BBL::BindJobStage::LoginStageConnect) {
|
||||
if (stage == BindJobStage::LoginStageConnect) {
|
||||
curr_percent = 15;
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageLogin) {
|
||||
} else if (stage == BindJobStage::LoginStageLogin) {
|
||||
curr_percent = 30;
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageWaitForLogin) {
|
||||
} else if (stage == BindJobStage::LoginStageWaitForLogin) {
|
||||
curr_percent = 45;
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageGetIdentify) {
|
||||
} else if (stage == BindJobStage::LoginStageGetIdentify) {
|
||||
curr_percent = 60;
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageWaitAuth) {
|
||||
} else if (stage == BindJobStage::LoginStageWaitAuth) {
|
||||
curr_percent = 80;
|
||||
msg = _u8L("Logging in");
|
||||
} else if (stage == BBL::BindJobStage::LoginStageFinished) {
|
||||
} else if (stage == BindJobStage::LoginStageFinished) {
|
||||
curr_percent = 100;
|
||||
msg = _u8L("Logging in");
|
||||
} else {
|
||||
|
||||
@@ -197,12 +197,12 @@ void PrintJob::process(Ctl &ctl)
|
||||
this->task_bed_type = bed_type_to_gcode_string(plate_data.is_valid ? plate_data.bed_type : curr_plate->get_bed_type(true));
|
||||
}
|
||||
|
||||
BBL::PrintParams params;
|
||||
PrintParams params;
|
||||
|
||||
// local print access
|
||||
params.dev_ip = m_dev_ip;
|
||||
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
|
||||
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt;
|
||||
params.use_ssl_for_mqtt = m_local_use_ssl;
|
||||
params.username = "bblp";
|
||||
params.password = m_access_code;
|
||||
|
||||
@@ -382,14 +382,14 @@ void PrintJob::process(Ctl &ctl)
|
||||
StagePercentPoint
|
||||
](int stage, int code, std::string info) {
|
||||
|
||||
if (stage == BBL::SendingPrintJobStage::PrintingStageCreate && !is_try_lan_mode_failed) {
|
||||
if (stage == SendingPrintJobStage::PrintingStageCreate && !is_try_lan_mode_failed) {
|
||||
if (this->connection_type == "lan") {
|
||||
msg = _u8L("Sending print job over LAN");
|
||||
} else {
|
||||
msg = _u8L("Sending print job through cloud service");
|
||||
}
|
||||
}
|
||||
else if (stage == BBL::SendingPrintJobStage::PrintingStageUpload && !is_try_lan_mode_failed) {
|
||||
else if (stage == SendingPrintJobStage::PrintingStageUpload && !is_try_lan_mode_failed) {
|
||||
if (code >= 0 && code <= 100 && !info.empty()) {
|
||||
if (this->connection_type == "lan") {
|
||||
msg = _u8L("Sending print job over LAN");
|
||||
@@ -399,24 +399,24 @@ void PrintJob::process(Ctl &ctl)
|
||||
msg += format("(%s)", info);
|
||||
}
|
||||
}
|
||||
else if (stage == BBL::SendingPrintJobStage::PrintingStageWaiting) {
|
||||
else if (stage == SendingPrintJobStage::PrintingStageWaiting) {
|
||||
if (this->connection_type == "lan") {
|
||||
msg = _u8L("Sending print job over LAN");
|
||||
} else {
|
||||
msg = _u8L("Sending print job through cloud service");
|
||||
}
|
||||
}
|
||||
else if (stage == BBL::SendingPrintJobStage::PrintingStageRecord && !is_try_lan_mode) {
|
||||
else if (stage == SendingPrintJobStage::PrintingStageRecord && !is_try_lan_mode) {
|
||||
msg = _u8L("Sending print configuration");
|
||||
}
|
||||
else if (stage == BBL::SendingPrintJobStage::PrintingStageSending && !is_try_lan_mode) {
|
||||
else if (stage == SendingPrintJobStage::PrintingStageSending && !is_try_lan_mode) {
|
||||
if (this->connection_type == "lan") {
|
||||
msg = _u8L("Sending print job over LAN");
|
||||
} else {
|
||||
msg = _u8L("Sending print job through cloud service");
|
||||
}
|
||||
}
|
||||
else if (stage == BBL::SendingPrintJobStage::PrintingStageFinished) {
|
||||
else if (stage == SendingPrintJobStage::PrintingStageFinished) {
|
||||
msg = format(_u8L("Successfully sent. Will automatically jump to the device page in %ss"), info);
|
||||
if (m_print_job_completed_id == wxGetApp().plater()->get_send_calibration_finished_event()) {
|
||||
msg = format(_u8L("Successfully sent. Will automatically jump to the next page in %ss"), info);
|
||||
@@ -433,15 +433,15 @@ void PrintJob::process(Ctl &ctl)
|
||||
// update current percnet
|
||||
if (stage >= 0 && stage <= (int) PrintingStageFinished) {
|
||||
curr_percent = StagePercentPoint[stage];
|
||||
if ((stage == BBL::SendingPrintJobStage::PrintingStageUpload
|
||||
|| stage == BBL::SendingPrintJobStage::PrintingStageRecord)
|
||||
if ((stage == SendingPrintJobStage::PrintingStageUpload
|
||||
|| stage == SendingPrintJobStage::PrintingStageRecord)
|
||||
&& (code > 0 && code <= 100)) {
|
||||
curr_percent = (StagePercentPoint[stage + 1] - StagePercentPoint[stage]) * code / 100 + StagePercentPoint[stage];
|
||||
}
|
||||
}
|
||||
|
||||
//get errors
|
||||
if (code > 100 || code < 0 || stage == BBL::SendingPrintJobStage::PrintingStageERROR) {
|
||||
if (code > 100 || code < 0 || stage == SendingPrintJobStage::PrintingStageERROR) {
|
||||
if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE) {
|
||||
m_plater->update_print_error_info(code, desc_file_too_large, info);
|
||||
}else if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_NOT_EXIST || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_NOT_EXIST){
|
||||
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
|
||||
int m_print_from_sdc_plate_idx = 0;
|
||||
|
||||
bool m_local_use_ssl_for_mqtt { true };
|
||||
bool m_local_use_ssl { true };
|
||||
bool m_local_use_ssl_for_ftp { true };
|
||||
bool task_bed_leveling;
|
||||
bool task_flow_cali;
|
||||
|
||||
@@ -100,10 +100,10 @@ inline std::string get_transform_string(int bytes)
|
||||
|
||||
void SendJob::process(Ctl &ctl)
|
||||
{
|
||||
BBL::PrintParams params;
|
||||
PrintParams params;
|
||||
std::string msg;
|
||||
int curr_percent = 10;
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
int result = -1;
|
||||
std::string http_body;
|
||||
@@ -183,7 +183,7 @@ void SendJob::process(Ctl &ctl)
|
||||
params.username = "bblp";
|
||||
params.password = m_access_code;
|
||||
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
|
||||
params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt;
|
||||
params.use_ssl_for_mqtt = m_local_use_ssl;
|
||||
wxString error_text;
|
||||
std::string msg_text;
|
||||
|
||||
@@ -234,14 +234,14 @@ void SendJob::process(Ctl &ctl)
|
||||
// update current percnet
|
||||
if (stage >= 0 && stage <= (int) PrintingStageFinished) {
|
||||
curr_percent = StagePercentPoint[stage];
|
||||
if ((stage == BBL::SendingPrintJobStage::PrintingStageUpload) &&
|
||||
if ((stage == SendingPrintJobStage::PrintingStageUpload) &&
|
||||
(code > 0 && code <= 100)) {
|
||||
curr_percent = (StagePercentPoint[stage + 1] - StagePercentPoint[stage]) * code / 100 + StagePercentPoint[stage];
|
||||
}
|
||||
}
|
||||
|
||||
//get errors
|
||||
if (code > 100 || code < 0 || stage == BBL::SendingPrintJobStage::PrintingStageERROR) {
|
||||
if (code > 100 || code < 0 || stage == SendingPrintJobStage::PrintingStageERROR) {
|
||||
if (code == BAMBU_NETWORK_ERR_PRINT_WR_FILE_OVER_SIZE || code == BAMBU_NETWORK_ERR_PRINT_SP_FILE_OVER_SIZE) {
|
||||
m_plater->update_print_error_info(code, desc_file_too_large, info);
|
||||
}
|
||||
@@ -281,7 +281,7 @@ void SendJob::process(Ctl &ctl)
|
||||
// try to send local with record
|
||||
BOOST_LOG_TRIVIAL(info) << "send_job: try to send gcode to printer";
|
||||
ctl.update_status(curr_percent, _u8L("Sending G-code file over LAN"));
|
||||
result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
|
||||
result = agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
|
||||
if (result == BAMBU_NETWORK_ERR_FTP_UPLOAD_FAILED) {
|
||||
params.comments = "upload_failed";
|
||||
} else {
|
||||
@@ -304,8 +304,8 @@ void SendJob::process(Ctl &ctl)
|
||||
case DevStorage::SdcardState::HAS_SDCARD_ABNORMAL:
|
||||
if(this->has_sdcard) {
|
||||
// means the sdcard is abnormal but can be used option is enabled
|
||||
ctl.update_status(curr_percent, _u8L("Sending G-code file over LAN, but the Storage in the printer is abnormal and print-issues may be caused by this."));
|
||||
result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
|
||||
ctl.update_status(curr_percent, _u8L("Sending G-code file over LAN, but the Storage in the printer is abnormal and print-issues may be caused by this."));
|
||||
result = agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
|
||||
break;
|
||||
}
|
||||
ctl.update_status(curr_percent, _u8L("The Storage in the printer is abnormal. Please replace it with a normal Storage before sending to printer."));
|
||||
@@ -315,7 +315,7 @@ void SendJob::process(Ctl &ctl)
|
||||
return;
|
||||
case DevStorage::SdcardState::HAS_SDCARD_NORMAL:
|
||||
ctl.update_status(curr_percent, _u8L("Sending G-code file over LAN"));
|
||||
result = m_agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
|
||||
result = agent->start_send_gcode_to_sdcard(params, update_fn, cancel_fn, nullptr);
|
||||
break;
|
||||
default:
|
||||
ctl.update_status(curr_percent, _u8L("Encountered an unknown error with the Storage status. Please try again."));
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
std::string connection_type;
|
||||
|
||||
bool m_local_use_ssl_for_ftp{true};
|
||||
bool m_local_use_ssl_for_mqtt{true};
|
||||
bool m_local_use_ssl{true};
|
||||
bool cloud_print_only { false };
|
||||
bool has_sdcard { false };
|
||||
bool task_use_ams { true };
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "I18N.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "DownloadProgressDialog.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/button.h>
|
||||
@@ -532,8 +533,8 @@ void MonitorPanel::update_network_version_footer()
|
||||
return;
|
||||
|
||||
std::string configured_version = wxGetApp().app_config->get_network_plugin_version();
|
||||
std::string suffix = BBL::extract_suffix(configured_version);
|
||||
std::string configured_base = BBL::extract_base_version(configured_version);
|
||||
std::string suffix = extract_suffix(configured_version);
|
||||
std::string configured_base = extract_base_version(configured_version);
|
||||
|
||||
wxString footer_text;
|
||||
if (!suffix.empty() && configured_base == binary_version) {
|
||||
|
||||
@@ -211,7 +211,7 @@ void NetworkPluginDownloadDialog::setup_version_selector()
|
||||
wxDefaultPosition, wxSize(FromDIP(380), FromDIP(28)), 0, nullptr, wxCB_READONLY);
|
||||
m_version_combo->SetFont(::Label::Body_13);
|
||||
|
||||
m_available_versions = BBL::get_all_available_versions();
|
||||
m_available_versions = get_all_available_versions();
|
||||
for (size_t i = 0; i < m_available_versions.size(); ++i) {
|
||||
const auto& ver = m_available_versions[i];
|
||||
wxString label;
|
||||
|
||||
@@ -53,7 +53,7 @@ private:
|
||||
wxCollapsiblePane* m_details_pane{nullptr};
|
||||
std::string m_error_message;
|
||||
std::string m_error_details;
|
||||
std::vector<BBL::NetworkLibraryVersionInfo> m_available_versions;
|
||||
std::vector<NetworkLibraryVersionInfo> m_available_versions;
|
||||
};
|
||||
|
||||
class NetworkPluginRestartDialog : public DPIDialog
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "format.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
@@ -124,8 +125,22 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog()
|
||||
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
|
||||
{
|
||||
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (opt_key == "host_type" || opt_key == "printhost_authorization_type")
|
||||
// Special handling for printer_agent: convert fake enum index to string agent ID
|
||||
if (opt_key == "printer_agent") {
|
||||
try {
|
||||
int selected_idx = boost::any_cast<int>(value);
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
if (selected_idx >= 0 && selected_idx < static_cast<int>(agents.size())) {
|
||||
m_config->set_key_value("printer_agent",
|
||||
new ConfigOptionString(agents[selected_idx].id));
|
||||
}
|
||||
} catch (const boost::bad_any_cast&) {
|
||||
// If value is not an int, ignore
|
||||
}
|
||||
this->update();
|
||||
} else if (opt_key == "host_type" || opt_key == "printhost_authorization_type") {
|
||||
this->update();
|
||||
}
|
||||
if (opt_key == "print_host")
|
||||
this->update_printhost_buttons();
|
||||
if (opt_key == "printhost_port")
|
||||
@@ -136,6 +151,47 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
|
||||
m_optgroup->append_single_option_line("host_type");
|
||||
|
||||
// Build printer agent dropdown from registry (only if network agent is available)
|
||||
if (wxGetApp().getAgent() != nullptr) {
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
|
||||
if (!agents.empty()) {
|
||||
// Create a fake enum option to force a Choice widget instead of TextCtrl
|
||||
// (printer_agent is coString in config, but we need a dropdown)
|
||||
ConfigOptionDef def;
|
||||
def.type = coEnum;
|
||||
def.width = Field::def_width();
|
||||
def.label = L("Printer Agent");
|
||||
def.tooltip = L("Select the network agent implementation for printer communication. "
|
||||
"Available agents are registered at startup.");
|
||||
def.mode = comAdvanced;
|
||||
|
||||
// Populate enum values and labels from registered agents
|
||||
for (const auto& agent : agents) {
|
||||
def.enum_values.push_back(agent.id);
|
||||
def.enum_labels.push_back(agent.display_name);
|
||||
}
|
||||
|
||||
// Resolve selected agent: use config value if valid, otherwise fall back to default
|
||||
std::string selected_agent = m_config->opt_string("printer_agent");
|
||||
auto it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
|
||||
if (it == agents.end()) {
|
||||
selected_agent = ORCA_PRINTER_AGENT_ID;
|
||||
it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
|
||||
}
|
||||
|
||||
if (it != agents.end()) {
|
||||
size_t default_idx = std::distance(agents.begin(), it);
|
||||
def.set_default_value(new ConfigOptionInt(static_cast<int>(default_idx)));
|
||||
}
|
||||
|
||||
// Create and append the option line
|
||||
auto agent_option = Option(def, "printer_agent");
|
||||
Line agent_line = m_optgroup->create_single_option_line(agent_option);
|
||||
m_optgroup->append_line(agent_line);
|
||||
}
|
||||
}
|
||||
|
||||
auto create_sizer_with_btn = [](wxWindow* parent, Button** btn, const std::string& icon_name, const wxString& label) {
|
||||
*btn = new Button(parent, label);
|
||||
(*btn)->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
|
||||
@@ -688,6 +744,31 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_printer_agent_type()
|
||||
{
|
||||
if (m_config == nullptr)
|
||||
return;
|
||||
|
||||
Field* agent_field = m_optgroup->get_field("printer_agent");
|
||||
if (!agent_field)
|
||||
return;
|
||||
|
||||
Choice* agent_choice = dynamic_cast<Choice*>(agent_field);
|
||||
if (!agent_choice)
|
||||
return;
|
||||
|
||||
// Sync selection with current config value
|
||||
const std::string current_agent = m_config->opt_string("printer_agent");
|
||||
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
for (size_t i = 0; i < agents.size(); ++i) {
|
||||
if (agents[i].id == current_agent) {
|
||||
agent_choice->set_value(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_printers()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
@@ -739,8 +820,13 @@ void PhysicalPrinterDialog::check_host_key_valid()
|
||||
|
||||
void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
||||
{
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name );
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name);
|
||||
event.Skip();
|
||||
|
||||
// Defer printer agent switch to ensure preset save completes first
|
||||
wxGetApp().CallAfter([] {
|
||||
wxGetApp().switch_printer_agent();
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -60,6 +60,7 @@ public:
|
||||
|
||||
void update(bool printer_change = false);
|
||||
void update_host_type(bool printer_change);
|
||||
void update_printer_agent_type();
|
||||
void update_preset_input();
|
||||
void update_printhost_buttons();
|
||||
void update_printers();
|
||||
|
||||
+101
-15
@@ -1985,6 +1985,8 @@ Sidebar::Sidebar(Plater *parent)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
// Orca: Sidebar - Filament titlebar UI
|
||||
// add filament title
|
||||
p->m_panel_filament_title = new StaticBox(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_NONE);
|
||||
p->m_panel_filament_title->SetBackgroundColor(title_bg);
|
||||
@@ -2094,7 +2096,8 @@ Sidebar::Sidebar(Plater *parent)
|
||||
//wxBoxSizer* bSizer_filament_content;
|
||||
//bSizer_filament_content = new wxBoxSizer( wxHORIZONTAL );
|
||||
|
||||
// BBS: filament double columns
|
||||
// Orca: Sidebar - Filament content UI: setup filament selection combos panel layout
|
||||
// Creates a two-column grid layout for filament selection dropdowns within the scrollable panel
|
||||
p->sizer_filaments = new wxBoxSizer(wxHORIZONTAL);
|
||||
p->sizer_filaments->Add(new wxBoxSizer(wxVERTICAL), 1, wxEXPAND);
|
||||
p->sizer_filaments->Add(new wxBoxSizer(wxVERTICAL), 1, wxEXPAND);
|
||||
@@ -2371,7 +2374,14 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
} else {
|
||||
//p->btn_connect_printer->Show();
|
||||
p->m_printer_connect->Show();
|
||||
p->m_bpButton_ams_filament->Hide();
|
||||
|
||||
// ORCA: show/hide sync-ams button based on filament sync mode
|
||||
auto agent = wxGetApp().getAgent();
|
||||
if (agent && agent->get_filament_sync_mode() != FilamentSyncMode::none)
|
||||
p->m_bpButton_ams_filament->Show();
|
||||
else
|
||||
p->m_bpButton_ams_filament->Hide();
|
||||
|
||||
auto print_btn_type = MainFrame::PrintSelectType::eExportGcode;
|
||||
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
|
||||
wxString apikey;
|
||||
@@ -3161,11 +3171,51 @@ void Sidebar::on_bed_type_change(BedType bed_type)
|
||||
p->combo_printer_bed->SetSelection(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a map of filament configurations from the connected printer's AMS (Automatic Material System).
|
||||
*
|
||||
* Data Flow Architecture:
|
||||
* =======================
|
||||
* This function reads pre-populated state from MachineObject - it does NOT directly call
|
||||
* NetworkAgent APIs. The data pipeline is:
|
||||
*
|
||||
* Printer Device (MQTT/LAN messages)
|
||||
* ↓
|
||||
* NetworkAgent (receives JSON, triggers OnMessageFn callbacks)
|
||||
* ↓
|
||||
* MachineObject::parse_json() (updates device state)
|
||||
* ├── vt_slot (std::vector<DevAmsTray>) - virtual tray data for external filament
|
||||
* └── DevFilaSystem → DevAms → DevAmsTray - AMS unit hierarchy
|
||||
* ↓
|
||||
* build_filament_ams_list() [THIS FUNCTION] - aggregates into DynamicPrintConfig maps
|
||||
*
|
||||
* Data Sources:
|
||||
* - obj->vt_slot: Virtual trays for external/manual filament loading (when ams_support_virtual_tray is true)
|
||||
* - obj->GetFilaSystem()->GetAmsList(): Map of AMS units, each containing multiple DevAmsTray slots
|
||||
*
|
||||
* Return Value:
|
||||
* - Map key encoding:
|
||||
* - Virtual trays: 0x10000 + vt_tray.id (first/main extruder), or just vt_tray.id (secondary)
|
||||
* - AMS trays: 0x10000 + (ams_id * 4 + slot_id) (main extruder), or (ams_id * 4 + slot_id) (secondary)
|
||||
* - The 0x10000 flag indicates the main/right extruder
|
||||
* - Map value: DynamicPrintConfig with filament properties (id, type, color, etc.)
|
||||
*
|
||||
* @param obj The MachineObject representing the connected printer (nullable)
|
||||
* @return Map of tray indices to filament configurations
|
||||
*/
|
||||
std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject* obj)
|
||||
{
|
||||
std::map<int, DynamicPrintConfig> filament_ams_list;
|
||||
if (!obj) return filament_ams_list;
|
||||
|
||||
// For pull-mode agents (e.g., HTTP REST API), refresh DevFilaSystem first
|
||||
auto* agent = wxGetApp().getDeviceManager()->get_agent();
|
||||
if (agent && agent->get_filament_sync_mode() == FilamentSyncMode::pull) {
|
||||
if (!agent->fetch_filament_info(obj->get_dev_id())) {
|
||||
return filament_ams_list;
|
||||
}
|
||||
}
|
||||
|
||||
auto build_tray_config = [](DevAmsTray const &tray, std::string const &name, std::string ams_id, std::string slot_id) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("build_filament_ams_list: name %1% setting_id %2% type %3% color %4%")
|
||||
% name % tray.setting_id % tray.m_fila_type % tray.color;
|
||||
@@ -3180,6 +3230,7 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
|
||||
tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{});
|
||||
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)});
|
||||
tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists});
|
||||
tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{tray.is_slot_placeholder});
|
||||
std::optional<FilamentBaseInfo> info;
|
||||
if (wxGetApp().preset_bundle) {
|
||||
info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id);
|
||||
@@ -3285,7 +3336,14 @@ void Sidebar::get_small_btn_sync_pos_size(wxPoint &pt, wxSize &size) {
|
||||
|
||||
void Sidebar::load_ams_list(MachineObject* obj)
|
||||
{
|
||||
std::map<int, DynamicPrintConfig> filament_ams_list = build_filament_ams_list(obj);
|
||||
std::map<int, DynamicPrintConfig> filament_ams_list;
|
||||
|
||||
// build_filament_ams_list handles both subscription-based and non-subscription-based agents:
|
||||
// - For non-subscription agents, it calls fetch_filament_info() first to populate DevFilaSystem
|
||||
// - Then it always reads from DevFilaSystem to build the filament list
|
||||
if (obj) {
|
||||
filament_ams_list = build_filament_ams_list(obj);
|
||||
}
|
||||
|
||||
bool device_change = false;
|
||||
const std::string& device = obj ? obj->get_dev_id() : "";
|
||||
@@ -3315,8 +3373,9 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
wxBusyCursor cursor;
|
||||
// Force load ams list
|
||||
auto obj = wxGetApp().getDeviceManager()->get_selected_machine();
|
||||
if (obj)
|
||||
GUI::wxGetApp().sidebar().load_ams_list(obj);
|
||||
if (!obj)
|
||||
return;
|
||||
GUI::wxGetApp().sidebar().load_ams_list(obj);
|
||||
|
||||
auto & list = wxGetApp().preset_bundle->filament_ams_list;
|
||||
if (list.empty()) {
|
||||
@@ -3414,6 +3473,16 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
// Replace unknown filament IDs with the resolved preset's filament_id
|
||||
auto &filaments = wxGetApp().preset_bundle->filaments;
|
||||
auto &filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||
for (size_t i = 0; i < list2.size() && i < filament_presets.size(); ++i) {
|
||||
if (list2[i] == UNKNOWN_FILAMENT_ID) {
|
||||
const Preset *resolved = filaments.find_preset(filament_presets[i]);
|
||||
if (resolved)
|
||||
list2[i] = resolved->filament_id;
|
||||
}
|
||||
}
|
||||
ams_filament_ids = boost::algorithm::join(list2, ",");
|
||||
wxGetApp().app_config ->set("ams_filament_ids", p->ams_list_device, ams_filament_ids);
|
||||
if (!unknowns.empty()) {
|
||||
@@ -3446,6 +3515,14 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
auto_calc_flushing_volumes(i);
|
||||
}
|
||||
}
|
||||
Layout();
|
||||
|
||||
// Perform preset selection and list update first — these may rebuild combo widgets,
|
||||
// which clears any badge state. Badges must be set AFTER these calls to persist.
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0]);
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
update_dynamic_filament_list();
|
||||
|
||||
auto badge_combox_filament = [](PlaterPresetComboBox *c) {
|
||||
auto tip = _L("Filament type and color information have been synchronized, but slot information is not included.");
|
||||
c->SetToolTip(tip);
|
||||
@@ -3454,8 +3531,15 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
{ // badge ams filament
|
||||
clear_combos_filament_badge();
|
||||
if (sync_result.direct_sync) {
|
||||
for (auto &c : p->combos_filament) {
|
||||
badge_combox_filament(c);
|
||||
auto& ams_list = wxGetApp().preset_bundle->filament_ams_list;
|
||||
size_t tray_idx = 0;
|
||||
for (auto& entry : ams_list) {
|
||||
if (tray_idx >= p->combos_filament.size()) break;
|
||||
auto filament_id = entry.second.opt_string("filament_id", 0u);
|
||||
if (!filament_id.empty()) {
|
||||
badge_combox_filament(p->combos_filament[tray_idx]);
|
||||
}
|
||||
tray_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3512,11 +3596,6 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
}
|
||||
}
|
||||
}
|
||||
Layout();
|
||||
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0]);
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
update_dynamic_filament_list();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "begin pop_finsish_sync_ams_dialog";
|
||||
pop_finsish_sync_ams_dialog();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "finish pop_finsish_sync_ams_dialog";
|
||||
@@ -9747,7 +9826,7 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
update_sidebar();
|
||||
int old_sel = e.GetOldSelection();
|
||||
if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->use_bbl_device_tab() && new_sel == MainFrame::tpMonitor) {
|
||||
if (!wxGetApp().getAgent()) {
|
||||
if (!Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
e.Veto();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel;
|
||||
if (q) {
|
||||
@@ -15137,7 +15216,7 @@ int Plater::export_3mf(const boost::filesystem::path& output_path, SaveStrategy
|
||||
// set designInfo before export and reset after export
|
||||
if (wxGetApp().is_user_login()) {
|
||||
p->model.design_info = std::make_shared<ModelDesignInfo>();
|
||||
//p->model.design_info->Designer = wxGetApp().getAgent()->get_user_nickanme();
|
||||
//p->model.design_info->Designer = wxGetApp().getAgent()->get_user_nickname();
|
||||
p->model.design_info->Designer = "";
|
||||
p->model.design_info->DesignerUserId = wxGetApp().getAgent()->get_user_id();
|
||||
BOOST_LOG_TRIVIAL(trace) << "design_info prepare, designer = "<< "";
|
||||
@@ -16578,8 +16657,15 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
|
||||
{
|
||||
printer_name.Replace("Bambu Lab", "", false);
|
||||
wxString content;
|
||||
bool device_page = (wxGetApp().mainframe == nullptr) && (wxGetApp().mainframe->m_monitor->IsShown());
|
||||
if (type == PrinterWarningType::NOT_CONNECTED) {
|
||||
content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."), printer_name);
|
||||
if (device_page) {
|
||||
content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."),
|
||||
printer_name);
|
||||
} else {
|
||||
content = wxString::Format(
|
||||
_L("OrcaSlicer can't connect to %s. Please check if the printer is powered on and connected to the network."), printer_name);
|
||||
}
|
||||
} else if (type == PrinterWarningType::INCONSISTENT) {
|
||||
content = wxString::Format(_L("The currently connected printer on the device page is not %s. Please switch to %s before syncing."), printer_name, printer_name);
|
||||
} else if (type == PrinterWarningType::UNINSTALL_FILAMENT) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
#include "Widgets/RadioGroup.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "DownloadProgressDialog.hpp"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
@@ -1411,6 +1412,11 @@ void PreferencesDialog::create_items()
|
||||
auto item_system_sync = create_item_checkbox(_L("Update built-in Presets automatically."), "", "sync_system_preset");
|
||||
g_sizer->Add(item_system_sync);
|
||||
|
||||
auto item_token_storage = create_item_checkbox(_L("Use encrypted file for token storage"),
|
||||
_L("Store authentication tokens in an encrypted file instead of the system keychain. (Requires restart)"),
|
||||
SETTING_USE_ENCRYPTED_TOKEN_FILE);
|
||||
g_sizer->Add(item_token_storage);
|
||||
|
||||
//// ONLINE > Network plugin
|
||||
g_sizer->Add(create_item_title(_L("Network plugin")), 1, wxEXPAND);
|
||||
|
||||
@@ -1433,11 +1439,11 @@ void PreferencesDialog::create_items()
|
||||
|
||||
std::string current_version = app_config->get_network_plugin_version();
|
||||
if (current_version.empty()) {
|
||||
current_version = BBL::get_latest_network_version();
|
||||
current_version = get_latest_network_version();
|
||||
}
|
||||
int current_selection = 0;
|
||||
|
||||
m_available_versions = BBL::get_all_available_versions();
|
||||
m_available_versions = get_all_available_versions();
|
||||
|
||||
for (size_t i = 0; i < m_available_versions.size(); i++) {
|
||||
const auto& ver = m_available_versions[i];
|
||||
@@ -1468,7 +1474,7 @@ void PreferencesDialog::create_items()
|
||||
std::string new_version = selected_ver.version;
|
||||
std::string old_version = app_config->get_network_plugin_version();
|
||||
if (old_version.empty()) {
|
||||
old_version = BBL::get_latest_network_version();
|
||||
old_version = get_latest_network_version();
|
||||
}
|
||||
|
||||
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, new_version);
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
::TextInput *m_backup_interval_textinput = {nullptr};
|
||||
::ComboBox * m_network_version_combo = {nullptr};
|
||||
wxBoxSizer * m_network_version_sizer = {nullptr};
|
||||
std::vector<BBL::NetworkLibraryVersionInfo> m_available_versions;
|
||||
std::vector<NetworkLibraryVersionInfo> m_available_versions;
|
||||
|
||||
wxString m_developer_mode_def;
|
||||
wxString m_internal_developer_mode_def;
|
||||
|
||||
@@ -1844,10 +1844,10 @@ void InputIpAddressDialog::on_send_retry()
|
||||
m_send_job->m_access_code = str_access_code.ToStdString();
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
m_send_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
|
||||
#else
|
||||
m_send_job->m_local_use_ssl_for_mqtt = m_obj->local_use_ssl_for_mqtt;
|
||||
m_send_job->m_local_use_ssl = m_obj->local_use_ssl;
|
||||
m_send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2464,10 +2464,10 @@ void SelectMachineDialog::on_send_print()
|
||||
m_print_job->m_access_code = obj_->get_access_code();
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
m_print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
|
||||
m_print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
m_print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
#else
|
||||
m_print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
|
||||
m_print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt;
|
||||
m_print_job->m_local_use_ssl = obj_->local_use_ssl;
|
||||
#endif
|
||||
m_print_job->connection_type = obj_->connection_type();
|
||||
m_print_job->cloud_print_only = obj_->is_support_cloud_print_only;
|
||||
|
||||
@@ -439,9 +439,9 @@ void SendMultiMachinePage::refresh_user_device()
|
||||
Fit();
|
||||
}
|
||||
|
||||
BBL::PrintParams SendMultiMachinePage::request_params(MachineObject* obj)
|
||||
PrintParams SendMultiMachinePage::request_params(MachineObject* obj)
|
||||
{
|
||||
BBL::PrintParams params;
|
||||
PrintParams params;
|
||||
|
||||
//get all setting
|
||||
bool bed_leveling = app_config->get("print", "bed_leveling") == "1" ? true : false;
|
||||
@@ -734,7 +734,7 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event)
|
||||
}
|
||||
|
||||
|
||||
std::vector<BBL::PrintParams> print_params;
|
||||
std::vector<PrintParams> print_params;
|
||||
|
||||
for (auto it = m_device_items.begin(); it != m_device_items.end(); ++it) {
|
||||
auto obj = it->second->get_obj();
|
||||
@@ -742,7 +742,7 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event)
|
||||
if (obj && obj->is_online() && !obj->can_abort() && !obj->is_in_upgrading() && it->second->get_state_selected() == 1 && it->second->state_printable <= 2) {
|
||||
|
||||
if (!it->second->is_blocking_printing(obj)) {
|
||||
BBL::PrintParams params = request_params(obj);
|
||||
PrintParams params = request_params(obj);
|
||||
print_params.push_back(params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ public:
|
||||
void on_send(wxCommandEvent& event);
|
||||
bool Show(bool show);
|
||||
|
||||
BBL::PrintParams request_params(MachineObject* obj);
|
||||
PrintParams request_params(MachineObject* obj);
|
||||
|
||||
bool get_ams_mapping_result(std::string &mapping_array_str, std::string &mapping_array_str2, std::string &ams_mapping_info);
|
||||
wxBoxSizer* create_item_title(wxString title, wxWindow* parent, wxString tooltip);
|
||||
|
||||
@@ -958,10 +958,10 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
|
||||
m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
m_send_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
#else
|
||||
m_send_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
|
||||
m_send_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt;
|
||||
m_send_job->m_local_use_ssl = obj_->local_use_ssl;
|
||||
|
||||
#endif
|
||||
m_send_job->connection_type = obj_->connection_type();
|
||||
|
||||
@@ -2099,6 +2099,11 @@ void Tab::on_presets_changed()
|
||||
// Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
|
||||
wxGetApp().plater()->sidebar().update_presets(m_type);
|
||||
|
||||
// Check if printer agent needs switching
|
||||
if (m_type == Preset::TYPE_PRINTER) {
|
||||
wxGetApp().switch_printer_agent();
|
||||
}
|
||||
|
||||
bool is_bbl_vendor_preset = m_preset_bundle->is_bbl_vendor();
|
||||
if (is_bbl_vendor_preset) {
|
||||
wxGetApp().plater()->get_partplate_list().set_render_option(true, true);
|
||||
|
||||
@@ -62,7 +62,7 @@ TaskState parse_task_status(int status)
|
||||
|
||||
int TaskStateInfo::g_task_info_id = 0;
|
||||
|
||||
TaskStateInfo::TaskStateInfo(BBL::PrintParams param)
|
||||
TaskStateInfo::TaskStateInfo(PrintParams param)
|
||||
: m_state(TaskState::TS_PENDING)
|
||||
, m_params(param)
|
||||
, m_sending_percent(0)
|
||||
@@ -103,8 +103,8 @@ TaskStateInfo::TaskStateInfo(BBL::PrintParams param)
|
||||
int curr_percent = 0;
|
||||
if (stage >= 0 && stage <= (int)PrintingStageFinished) {
|
||||
curr_percent = StagePercentPoint[stage];
|
||||
if ((stage == BBL::SendingPrintJobStage::PrintingStageUpload
|
||||
|| stage == BBL::SendingPrintJobStage::PrintingStageRecord)
|
||||
if ((stage == SendingPrintJobStage::PrintingStageUpload
|
||||
|| stage == SendingPrintJobStage::PrintingStageRecord)
|
||||
&& (code > 0 && code <= 100)) {
|
||||
curr_percent = (StagePercentPoint[stage + 1] - StagePercentPoint[stage]) * code / 100 + StagePercentPoint[stage];
|
||||
BOOST_LOG_TRIVIAL(trace) << "task_manager: percent = " << curr_percent;
|
||||
@@ -156,7 +156,7 @@ TaskManager::TaskManager(NetworkAgent* agent)
|
||||
}
|
||||
|
||||
|
||||
int TaskManager::start_print(const std::vector<BBL::PrintParams>& params, TaskSettings* settings)
|
||||
int TaskManager::start_print(const std::vector<PrintParams>& params, TaskSettings* settings)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "task_manager: start_print size = " << params.size();
|
||||
TaskManager::MaxSendingAtSameTime = settings->max_sending_at_same_time;
|
||||
@@ -173,7 +173,7 @@ int TaskManager::start_print(const std::vector<BBL::PrintParams>& params, TaskSe
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_print_test(BBL::PrintParams& params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
static int start_print_test(PrintParams& params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
int tick = 2;
|
||||
for (int i = 0; i < 100 * tick; i++) {
|
||||
@@ -321,7 +321,7 @@ std::map<std::string, TaskStateInfo> TaskManager::get_task_list(int curr_page, i
|
||||
{
|
||||
std::map<std::string, TaskStateInfo> out;
|
||||
if (m_agent) {
|
||||
BBL::TaskQueryParams task_query_params;
|
||||
TaskQueryParams task_query_params;
|
||||
task_query_params.limit = page_count;
|
||||
task_query_params.offset = curr_page * page_count;
|
||||
std::string task_info;
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
static int g_task_info_id;
|
||||
typedef std::function<void(TaskState state, int percent)> StateChangedFn;
|
||||
|
||||
TaskStateInfo(const BBL::PrintParams param);
|
||||
TaskStateInfo(const PrintParams param);
|
||||
|
||||
TaskStateInfo() {
|
||||
task_info_id = ++TaskStateInfo::g_task_info_id;
|
||||
@@ -47,9 +47,9 @@ public:
|
||||
m_state_changed_fn(m_state, m_sending_percent);
|
||||
}
|
||||
}
|
||||
BBL::PrintParams get_params() { return m_params; }
|
||||
PrintParams get_params() { return m_params; }
|
||||
|
||||
BBL::PrintParams& params() { return m_params; }
|
||||
PrintParams& params() { return m_params; }
|
||||
|
||||
std::string get_job_id(){return profile_id;}
|
||||
|
||||
@@ -109,7 +109,7 @@ private:
|
||||
TaskState m_state;
|
||||
std::string m_task_name;
|
||||
std::string m_device_name;
|
||||
BBL::PrintParams m_params;
|
||||
PrintParams m_params;
|
||||
int m_sending_percent;
|
||||
std::string m_job_id;
|
||||
StateChangedFn m_state_changed_fn;
|
||||
@@ -147,7 +147,7 @@ public:
|
||||
static int SendingInterval;
|
||||
TaskManager(NetworkAgent* agent);
|
||||
|
||||
int start_print(const std::vector<BBL::PrintParams>& params, TaskSettings* settings = nullptr);
|
||||
int start_print(const std::vector<PrintParams>& params, TaskSettings* settings = nullptr);
|
||||
|
||||
static void set_max_send_at_same_time(int count);
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@ int ZUserLogin::web_sequence_id = 20000;
|
||||
ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_ANY, "OrcaSlicer")
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
const auto bblnetwork_enabled =wxGetApp().app_config->get_bool("installed_networking");
|
||||
// Url
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
if (!agent) {
|
||||
if (!agent && bblnetwork_enabled) {
|
||||
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
@@ -75,15 +76,11 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
|
||||
CentreOnParent();
|
||||
}
|
||||
else {
|
||||
std::string host_url = agent->get_bambulab_host();
|
||||
TargetUrl = host_url + "/sign-in";
|
||||
m_networkOk = false;
|
||||
|
||||
// Get the login URL from the cloud service agent
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "") {
|
||||
strlang.Replace("_", "-");
|
||||
TargetUrl = host_url + "/" + strlang + "/sign-in";
|
||||
}
|
||||
strlang.Replace("_", "-");
|
||||
TargetUrl = wxString::FromUTF8(agent->get_cloud_login_url(strlang.ToStdString()));
|
||||
m_networkOk = TargetUrl.StartsWith("file://");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "login url = " << TargetUrl.ToStdString();
|
||||
|
||||
@@ -225,9 +222,9 @@ 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_bambulab_host();
|
||||
std::string strHost = agent->get_cloud_service_host();
|
||||
|
||||
if ( tmpUrl.Contains(strHost) ) {
|
||||
if (tmpUrl.StartsWith("file://") || tmpUrl.Contains(strHost)) {
|
||||
m_networkOk = true;
|
||||
// wxLogMessage("%s", "Document loaded; url='" + evt.GetURL() + "'");
|
||||
}
|
||||
@@ -268,10 +265,56 @@ void ZUserLogin::OnFullScreenChanged(wxWebViewEvent &evt)
|
||||
void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
{
|
||||
wxString str_input = evt.GetString();
|
||||
|
||||
try {
|
||||
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()) {
|
||||
// Return login config (backend_url, apikey, pkce)
|
||||
// WebView handles provider selection internally
|
||||
std::string login_cmd = agent->build_login_cmd();
|
||||
m_loopback_port = 0;
|
||||
try {
|
||||
json cfg = json::parse(login_cmd);
|
||||
if (cfg.contains("pkce")) {
|
||||
const auto& pkce = cfg["pkce"];
|
||||
if (pkce.contains("loopback_port")) {
|
||||
if (pkce["loopback_port"].is_number_integer()) {
|
||||
m_loopback_port = pkce["loopback_port"].get<int>();
|
||||
} else if (pkce["loopback_port"].is_string()) {
|
||||
m_loopback_port = std::stoi(pkce["loopback_port"].get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_loopback_port <= 0 && pkce.contains("redirect_uri") && pkce["redirect_uri"].is_string()) {
|
||||
const std::string redirect_uri = pkce["redirect_uri"].get<std::string>();
|
||||
const char* prefixes[] = {"localhost:", "127.0.0.1:"};
|
||||
for (const char* prefix : prefixes) {
|
||||
auto start = redirect_uri.find(prefix);
|
||||
if (start == std::string::npos)
|
||||
continue;
|
||||
start += strlen(prefix);
|
||||
auto end = redirect_uri.find('/', start);
|
||||
std::string port_str = redirect_uri.substr(start, end - start);
|
||||
try {
|
||||
m_loopback_port = std::stoi(port_str);
|
||||
} catch (...) {
|
||||
m_loopback_port = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
m_loopback_port = 0;
|
||||
}
|
||||
wxString str_js = wxString::FromUTF8("window.postMessage(") + wxString::FromUTF8(login_cmd.c_str()) +
|
||||
wxString::FromUTF8(", '*')");
|
||||
this->RunScript(str_js);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strCmd == "autotest_token")
|
||||
{
|
||||
@@ -279,17 +322,26 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
}
|
||||
if (strCmd == "user_login") {
|
||||
j["data"]["autotest_token"] = m_AutotestToken;
|
||||
wxGetApp().handle_script_message(j.dump());
|
||||
Close();
|
||||
std::string message_json = j.dump();
|
||||
|
||||
// End modal dialog first to unblock event loop before processing callbacks
|
||||
EndModal(wxID_OK);
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
else if (strCmd == "get_localhost_url") {
|
||||
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_localhost_url";
|
||||
wxGetApp().start_http_server();
|
||||
int loopback_port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
|
||||
wxGetApp().start_http_server(loopback_port);
|
||||
std::string sequence_id = j["sequence_id"].get<std::string>();
|
||||
CallAfter([this, sequence_id] {
|
||||
json ack_j;
|
||||
ack_j["command"] = "get_localhost_url";
|
||||
ack_j["response"]["base_url"] = std::string(LOCALHOST_URL) + std::to_string(LOCALHOST_PORT);
|
||||
int loopback_port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
|
||||
ack_j["response"]["base_url"] = std::string(LOCALHOST_URL) + std::to_string(loopback_port);
|
||||
ack_j["response"]["result"] = "success";
|
||||
ack_j["sequence_id"] = sequence_id;
|
||||
wxString str_js = wxString::Format("window.postMessage(%s)", ack_j.dump());
|
||||
@@ -297,9 +349,10 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
|
||||
});
|
||||
}
|
||||
else if (strCmd == "thirdparty_login") {
|
||||
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: 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);
|
||||
CallAfter([this, jump_url] {
|
||||
wxString url = wxString::FromUTF8(jump_url);
|
||||
wxLaunchDefaultBrowser(url);
|
||||
|
||||
@@ -71,6 +71,7 @@ private:
|
||||
wxWebView *m_browser;
|
||||
|
||||
std::string m_AutotestToken;
|
||||
int m_loopback_port { 0 };
|
||||
|
||||
#if wxUSE_WEBVIEW_IE
|
||||
wxMenuItem *m_script_object_el;
|
||||
|
||||
@@ -487,21 +487,24 @@ void WebViewPanel::SendLoginInfo()
|
||||
|
||||
void WebViewPanel::ShowNetpluginTip()
|
||||
{
|
||||
// Install Network Plugin
|
||||
//std::string NP_Installed = wxGetApp().app_config->get("installed_networking");
|
||||
bool bValid = wxGetApp().is_compatibility_version();
|
||||
const auto bblnetwork_enabled = wxGetApp().app_config->get_bool("installed_networking");
|
||||
|
||||
int nShow = 0;
|
||||
if (!bValid) nShow = 1;
|
||||
// Show tip if: plugin is enabled but incompatible, OR BBL printer selected but plugin not loaded
|
||||
bool need_show = false;
|
||||
if (bblnetwork_enabled) {
|
||||
need_show = !wxGetApp().is_compatibility_version();
|
||||
} else if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||
need_show = true;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": bValid=%1%, nShow=%2%")%bValid %nShow;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": need_show=%1%") % need_show;
|
||||
|
||||
json m_Res = json::object();
|
||||
m_Res["command"] = "network_plugin_installtip";
|
||||
m_Res["sequence_id"] = "10001";
|
||||
m_Res["show"] = nShow;
|
||||
json res = json::object();
|
||||
res["command"] = "network_plugin_installtip";
|
||||
res["sequence_id"] = "10001";
|
||||
res["show"] = need_show ? 1 : 0;
|
||||
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||
|
||||
RunScript(strJS);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,861 @@
|
||||
#include "BBLCloudServiceAgent.hpp"
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BBLCloudServiceAgent::BBLCloudServiceAgent() = default;
|
||||
|
||||
BBLCloudServiceAgent::~BBLCloudServiceAgent() = default;
|
||||
|
||||
// ============================================================================
|
||||
// Lifecycle (merged from BBLAuthAgent)
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::init_log()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_init_log();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_config_dir(std::string config_dir)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_config_dir();
|
||||
if (func && agent) {
|
||||
return func(agent, config_dir);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_cert_file(std::string folder, std::string filename)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_cert_file();
|
||||
if (func && agent) {
|
||||
return func(agent, folder, filename);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_country_code(std::string country_code)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_country_code();
|
||||
if (func && agent) {
|
||||
return func(agent, country_code);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::start()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// User Session Management (merged from BBLAuthAgent)
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::change_user(std::string user_info)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_change_user();
|
||||
if (func && agent) {
|
||||
return func(agent, user_info);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool BBLCloudServiceAgent::is_user_login()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_is_user_login();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::user_logout(bool request)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_user_logout();
|
||||
if (func && agent) {
|
||||
return func(agent, request);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_user_id()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_id();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_user_name()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_name();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_user_avatar()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_avatar();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_user_nickname()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_nickanme();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Login UI Support (merged from BBLAuthAgent)
|
||||
// ============================================================================
|
||||
|
||||
std::string BBLCloudServiceAgent::build_login_cmd()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_build_login_cmd();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::build_logout_cmd()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_build_logout_cmd();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::build_login_info()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_build_login_info();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Token Access (merged from BBLAuthAgent)
|
||||
// ============================================================================
|
||||
|
||||
std::string BBLCloudServiceAgent::get_access_token() const
|
||||
{
|
||||
// BBL DLL manages tokens internally, not exposed via function pointer
|
||||
// Return empty string - BBL agents inject tokens automatically
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_refresh_token() const
|
||||
{
|
||||
// BBL DLL manages tokens internally, not exposed via function pointer
|
||||
return "";
|
||||
}
|
||||
|
||||
bool BBLCloudServiceAgent::ensure_token_fresh(const std::string& reason)
|
||||
{
|
||||
// BBL DLL handles token refresh internally
|
||||
// Always return true assuming the DLL manages this
|
||||
(void)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
|
||||
// ============================================================================
|
||||
|
||||
std::string BBLCloudServiceAgent::get_cloud_service_host()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_bambulab_host();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_cloud_login_url(const std::string& language)
|
||||
{
|
||||
std::string host_url = get_cloud_service_host();
|
||||
if (host_url.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (language.empty()) {
|
||||
return host_url + "/sign-in";
|
||||
}
|
||||
return host_url + "/" + language + "/sign-in";
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::connect_server()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_connect_server();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool BBLCloudServiceAgent::is_server_connected()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_is_server_connected();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::refresh_connection()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_refresh_connection();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::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 BBLCloudServiceAgent::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 BBLCloudServiceAgent::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 BBLCloudServiceAgent::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;
|
||||
}
|
||||
|
||||
void BBLCloudServiceAgent::enable_multi_machine(bool enable)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_enable_multi_machine();
|
||||
if (func && agent) {
|
||||
func(agent, enable);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Settings Synchronization
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_presets();
|
||||
if (func && agent) {
|
||||
return func(agent, user_presets);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_request_setting_id();
|
||||
if (func && agent) {
|
||||
return func(agent, name, values_map, http_code);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_put_setting();
|
||||
if (func && agent) {
|
||||
return func(agent, setting_id, name, values_map, http_code);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_setting_list(std::string bundle_version, ProgressFn pro_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_setting_list();
|
||||
if (func && agent) {
|
||||
return func(agent, bundle_version, pro_fn, cancel_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_setting_list2();
|
||||
if (func && agent) {
|
||||
return func(agent, bundle_version, chk_fn, pro_fn, cancel_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::delete_setting(std::string setting_id)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_delete_setting();
|
||||
if (func && agent) {
|
||||
return func(agent, setting_id);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cloud User Services
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_my_message();
|
||||
if (func && agent) {
|
||||
return func(agent, type, after, limit, http_code, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::check_user_task_report(int* task_id, bool* printable)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_check_user_task_report();
|
||||
if (func && agent) {
|
||||
return func(agent, task_id, printable);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_user_print_info(unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_print_info();
|
||||
if (func && agent) {
|
||||
return func(agent, http_code, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_user_tasks(TaskQueryParams params, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_tasks();
|
||||
if (func && agent) {
|
||||
return func(agent, params, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_printer_firmware();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, http_code, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_task_plate_index(std::string task_id, int* plate_index)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_task_plate_index();
|
||||
if (func && agent) {
|
||||
return func(agent, task_id, plate_index);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_user_info(int* identifier)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_info();
|
||||
if (func && agent) {
|
||||
return func(agent, identifier);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_subtask_info();
|
||||
if (func && agent) {
|
||||
return func(agent, subtask_id, task_json, http_code, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_slice_info();
|
||||
if (func && agent) {
|
||||
return func(agent, project_id, profile_id, plate_index, slice_json);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_query_bind_status();
|
||||
if (func && agent) {
|
||||
return func(agent, query_list, http_code, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::modify_printer_name(std::string dev_id, std::string dev_name)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_modify_printer_name();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, dev_name);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Model Mall & Publishing
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::get_camera_url(std::string dev_id, std::function<void(std::string)> callback)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_camera_url();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, callback);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_design_staffpick();
|
||||
if (func && agent) {
|
||||
return func(agent, offset, limit, callback);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_publish();
|
||||
if (func && agent) {
|
||||
return func(agent, params, update_fn, cancel_fn, out);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_model_publish_url(std::string* url)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_model_publish_url();
|
||||
if (func && agent) {
|
||||
return func(agent, url);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_subtask();
|
||||
if (func && agent) {
|
||||
return func(agent, task, getsub_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_model_mall_home_url(std::string* url)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_model_mall_home_url();
|
||||
if (func && agent) {
|
||||
return func(agent, url);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_model_mall_detail_url(std::string* url, std::string id)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_model_mall_detail_url();
|
||||
if (func && agent) {
|
||||
return func(agent, url, id);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_my_profile(std::string token, unsigned int* http_code, std::string* http_body)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_my_profile();
|
||||
if (func && agent) {
|
||||
return func(agent, token, http_code, http_body);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Analytics & Tracking
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::track_enable(bool enable)
|
||||
{
|
||||
m_enable_track = enable;
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_track_enable();
|
||||
if (func && agent) {
|
||||
return func(agent, enable);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::track_remove_files()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_track_remove_files();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::track_event(std::string evt_key, std::string content)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_track_event();
|
||||
if (func && agent) {
|
||||
return func(agent, evt_key, content);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::track_header(std::string header)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_track_header();
|
||||
if (func && agent) {
|
||||
return func(agent, header);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::track_update_property(std::string name, std::string value, std::string type)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_track_update_property();
|
||||
if (func && agent) {
|
||||
return func(agent, name, value, type);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::track_get_property(std::string name, std::string& value, std::string type)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_track_get_property();
|
||||
if (func && agent) {
|
||||
return func(agent, name, value, type);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool BBLCloudServiceAgent::get_track_enable()
|
||||
{
|
||||
return m_enable_track;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Ratings & Reviews
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::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)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_put_model_mall_rating();
|
||||
if (func && agent) {
|
||||
return func(agent, design_id, score, content, images, http_code, http_error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_oss_config(std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_oss_config();
|
||||
if (func && agent) {
|
||||
return func(agent, config, country_code, http_code, http_error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::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)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_put_rating_picture_oss();
|
||||
if (func && agent) {
|
||||
return func(agent, config, pic_oss_path, model_id, profile_id, http_code, http_error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_model_mall_rating_result(int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_model_mall_rating_result();
|
||||
if (func && agent) {
|
||||
return func(agent, job_id, rating_result, http_code, http_error);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Extra Features
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::set_extra_http_header(std::map<std::string, std::string> extra_headers)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_extra_http_header();
|
||||
if (func && agent) {
|
||||
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();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_mw_user_preference();
|
||||
if (func && agent) {
|
||||
return func(agent, callback);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_mw_user_4ulist();
|
||||
if (func && agent) {
|
||||
return func(agent, seed, limit, callback);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string BBLCloudServiceAgent::get_version()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto func = plugin.get_get_version();
|
||||
if (func) {
|
||||
return func();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cloud Callbacks
|
||||
// ============================================================================
|
||||
|
||||
int BBLCloudServiceAgent::set_on_server_connected_fn(OnServerConnectedFn 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);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_on_http_error_fn(OnHttpErrorFn 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);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_get_country_code_fn(GetCountryCodeFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_get_country_code_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_queue_on_main_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,136 @@
|
||||
#ifndef __BBL_CLOUD_SERVICE_AGENT_HPP__
|
||||
#define __BBL_CLOUD_SERVICE_AGENT_HPP__
|
||||
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* BBLCloudServiceAgent - BBL DLL wrapper implementation of ICloudServiceAgent.
|
||||
*
|
||||
* Delegates all cloud service and authentication operations to the proprietary
|
||||
* BBL network DLL through function pointers obtained from BBLNetworkPlugin singleton.
|
||||
* This class combines the functionality of the former BBLAuthAgent and BBLCloudServiceAgent.
|
||||
*/
|
||||
class BBLCloudServiceAgent : public ICloudServiceAgent {
|
||||
public:
|
||||
BBLCloudServiceAgent();
|
||||
~BBLCloudServiceAgent() override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Auth Methods
|
||||
// ========================================================================
|
||||
|
||||
// Lifecycle
|
||||
int init_log() override;
|
||||
int set_config_dir(std::string config_dir) override;
|
||||
int set_cert_file(std::string folder, std::string filename) override;
|
||||
int set_country_code(std::string country_code) override;
|
||||
int start() override;
|
||||
|
||||
// User Session Management
|
||||
int change_user(std::string user_info) override;
|
||||
bool is_user_login() override;
|
||||
int user_logout(bool request = false) override;
|
||||
std::string get_user_id() override;
|
||||
std::string get_user_name() override;
|
||||
std::string get_user_avatar() override;
|
||||
std::string get_user_nickname() override;
|
||||
|
||||
// Login UI Support
|
||||
std::string build_login_cmd() override;
|
||||
std::string build_logout_cmd() override;
|
||||
std::string build_login_info() override;
|
||||
|
||||
// Token Access (BBL manages tokens internally)
|
||||
std::string get_access_token() const override;
|
||||
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
|
||||
// ========================================================================
|
||||
|
||||
// Server Connectivity
|
||||
std::string get_cloud_service_host() override;
|
||||
std::string get_cloud_login_url(const std::string& language = "") override;
|
||||
int connect_server() override;
|
||||
bool is_server_connected() override;
|
||||
int refresh_connection() override;
|
||||
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;
|
||||
void enable_multi_machine(bool enable) override;
|
||||
|
||||
// Settings Synchronization
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets) override;
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int delete_setting(std::string setting_id) override;
|
||||
|
||||
// Cloud User Services
|
||||
int get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body) override;
|
||||
int check_user_task_report(int* task_id, bool* printable) override;
|
||||
int get_user_print_info(unsigned int* http_code, std::string* http_body) override;
|
||||
int get_user_tasks(TaskQueryParams params, std::string* http_body) override;
|
||||
int get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body) override;
|
||||
int get_task_plate_index(std::string task_id, int* plate_index) override;
|
||||
int get_user_info(int* identifier) override;
|
||||
int get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body) override;
|
||||
int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json) override;
|
||||
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body) override;
|
||||
int modify_printer_name(std::string dev_id, std::string dev_name) override;
|
||||
|
||||
// Model Mall & Publishing
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) override;
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) override;
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override;
|
||||
int get_model_publish_url(std::string* url) override;
|
||||
int get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn) override;
|
||||
int get_model_mall_home_url(std::string* url) override;
|
||||
int get_model_mall_detail_url(std::string* url, std::string id) override;
|
||||
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body) override;
|
||||
|
||||
// Analytics & Tracking
|
||||
int track_enable(bool enable) override;
|
||||
int track_remove_files() override;
|
||||
int track_event(std::string evt_key, std::string content) override;
|
||||
int track_header(std::string header) override;
|
||||
int track_update_property(std::string name, std::string value, std::string type = "string") override;
|
||||
int track_get_property(std::string name, std::string& value, std::string type = "string") override;
|
||||
bool get_track_enable() override;
|
||||
|
||||
// Ratings & Reviews
|
||||
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) override;
|
||||
int get_oss_config(std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error) override;
|
||||
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) override;
|
||||
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_get_country_code_fn(GetCountryCodeFn fn) override;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
|
||||
private:
|
||||
bool m_enable_track{false};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __BBL_CLOUD_SERVICE_AGENT_HPP__
|
||||
@@ -0,0 +1,790 @@
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/Utils/FileTransferUtils.hpp"
|
||||
|
||||
#if !defined(_MSC_VER) && !defined(_WIN32)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#define BAMBU_SOURCE_LIBRARY "BambuSource"
|
||||
|
||||
// ============================================================================
|
||||
// Singleton Implementation
|
||||
// ============================================================================
|
||||
|
||||
// Static pointer initialization (null by default, created on first access)
|
||||
BBLNetworkPlugin* BBLNetworkPlugin::s_instance = nullptr;
|
||||
|
||||
BBLNetworkPlugin& BBLNetworkPlugin::instance()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [] {
|
||||
s_instance = new BBLNetworkPlugin();
|
||||
});
|
||||
return *s_instance;
|
||||
}
|
||||
|
||||
void BBLNetworkPlugin::shutdown()
|
||||
{
|
||||
// Note: Do not call instance() after shutdown() - the singleton is destroyed.
|
||||
if (s_instance) {
|
||||
delete s_instance;
|
||||
s_instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
BBLNetworkPlugin::BBLNetworkPlugin() = default;
|
||||
|
||||
BBLNetworkPlugin::~BBLNetworkPlugin()
|
||||
{
|
||||
destroy_agent();
|
||||
unload();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Module Lifecycle
|
||||
// ============================================================================
|
||||
|
||||
int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version)
|
||||
{
|
||||
clear_load_error();
|
||||
|
||||
std::string library;
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto plugin_folder = data_dir_path / "plugins";
|
||||
|
||||
if (using_backup) {
|
||||
plugin_folder = plugin_folder / "backup";
|
||||
}
|
||||
|
||||
if (version.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": version is required but not provided";
|
||||
set_load_error(
|
||||
"Network library version not specified",
|
||||
"A version must be specified to load the network library",
|
||||
""
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Auto-migration: If loading legacy version and versioned library doesn't exist,
|
||||
// but unversioned legacy library does exist, rename it to versioned format
|
||||
if (version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) {
|
||||
boost::filesystem::path versioned_path;
|
||||
boost::filesystem::path legacy_path;
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
versioned_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll");
|
||||
legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
|
||||
#elif defined(__WXMAC__)
|
||||
versioned_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib");
|
||||
legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
|
||||
#else
|
||||
versioned_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so");
|
||||
legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
|
||||
#endif
|
||||
if (!boost::filesystem::exists(versioned_path) && boost::filesystem::exists(legacy_path)) {
|
||||
try {
|
||||
boost::filesystem::rename(legacy_path, versioned_path);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to rename legacy library: " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load versioned library
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll";
|
||||
#else
|
||||
#if defined(__WXMAC__)
|
||||
std::string lib_ext = ".dylib";
|
||||
#else
|
||||
std::string lib_ext = ".so";
|
||||
#endif
|
||||
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + lib_ext;
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
wchar_t lib_wstr[256];
|
||||
memset(lib_wstr, 0, sizeof(lib_wstr));
|
||||
::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
|
||||
m_networking_module = LoadLibrary(lib_wstr);
|
||||
if (!m_networking_module) {
|
||||
std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_NETWORK_LIBRARY));
|
||||
if (library_path.empty()) {
|
||||
set_load_error(
|
||||
"Network library not found",
|
||||
"Could not locate versioned library: " + library,
|
||||
library
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
memset(lib_wstr, 0, sizeof(lib_wstr));
|
||||
::MultiByteToWideChar(CP_UTF8, NULL, library_path.c_str(), strlen(library_path.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
|
||||
m_networking_module = LoadLibrary(lib_wstr);
|
||||
}
|
||||
#else
|
||||
m_networking_module = dlopen(library.c_str(), RTLD_LAZY);
|
||||
if (!m_networking_module) {
|
||||
char* dll_error = dlerror();
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": dlopen failed: " << (dll_error ? dll_error : "unknown error");
|
||||
set_load_error(
|
||||
"Failed to load network library",
|
||||
dll_error ? std::string(dll_error) : "Unknown dlopen error",
|
||||
library
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m_networking_module) {
|
||||
if (!m_load_error.has_error) {
|
||||
set_load_error(
|
||||
"Network library failed to load",
|
||||
"LoadLibrary/dlopen returned null",
|
||||
library
|
||||
);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Load file transfer interface
|
||||
InitFTModule(m_networking_module);
|
||||
|
||||
// Load all function pointers
|
||||
load_all_function_pointers();
|
||||
|
||||
if (m_get_version) {
|
||||
(void) m_get_version();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int BBLNetworkPlugin::unload()
|
||||
{
|
||||
UnloadFTModule();
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
if (m_networking_module) {
|
||||
FreeLibrary(m_networking_module);
|
||||
m_networking_module = NULL;
|
||||
}
|
||||
if (m_source_module) {
|
||||
FreeLibrary(m_source_module);
|
||||
m_source_module = NULL;
|
||||
}
|
||||
#else
|
||||
if (m_networking_module) {
|
||||
dlclose(m_networking_module);
|
||||
m_networking_module = NULL;
|
||||
}
|
||||
if (m_source_module) {
|
||||
dlclose(m_source_module);
|
||||
m_source_module = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
clear_all_function_pointers();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BBLNetworkPlugin::is_loaded() const
|
||||
{
|
||||
return m_networking_module != nullptr;
|
||||
}
|
||||
|
||||
std::string BBLNetworkPlugin::get_version() const
|
||||
{
|
||||
bool consistent = true;
|
||||
// Check the debug consistent first
|
||||
if (m_check_debug_consistent) {
|
||||
#if defined(NDEBUG)
|
||||
consistent = m_check_debug_consistent(false);
|
||||
#else
|
||||
consistent = m_check_debug_consistent(true);
|
||||
#endif
|
||||
}
|
||||
if (!consistent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", inconsistent library, return 00.00.00.00!");
|
||||
return "00.00.00.00";
|
||||
}
|
||||
if (m_get_version) {
|
||||
return m_get_version();
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", get_version not supported, return 00.00.00.00!");
|
||||
return "00.00.00.00";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Lifecycle
|
||||
// ============================================================================
|
||||
|
||||
void* BBLNetworkPlugin::create_agent(const std::string& log_dir)
|
||||
{
|
||||
if (m_agent) {
|
||||
return m_agent;
|
||||
}
|
||||
|
||||
if (m_create_agent) {
|
||||
m_agent = m_create_agent(log_dir);
|
||||
}
|
||||
|
||||
return m_agent;
|
||||
}
|
||||
|
||||
int BBLNetworkPlugin::destroy_agent()
|
||||
{
|
||||
int ret = 0;
|
||||
if (m_agent && m_destroy_agent) {
|
||||
ret = m_destroy_agent(m_agent);
|
||||
}
|
||||
m_agent = nullptr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DLL Module Accessors
|
||||
// ============================================================================
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
HMODULE BBLNetworkPlugin::get_source_module()
|
||||
#else
|
||||
void* BBLNetworkPlugin::get_source_module()
|
||||
#endif
|
||||
{
|
||||
if ((m_source_module) || (!m_networking_module))
|
||||
return m_source_module;
|
||||
|
||||
std::string library;
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto plugin_folder = data_dir_path / "plugins";
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
wchar_t lib_wstr[128];
|
||||
|
||||
library = plugin_folder.string() + "/" + std::string(BAMBU_SOURCE_LIBRARY) + ".dll";
|
||||
memset(lib_wstr, 0, sizeof(lib_wstr));
|
||||
::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
|
||||
m_source_module = LoadLibrary(lib_wstr);
|
||||
if (!m_source_module) {
|
||||
std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_SOURCE_LIBRARY));
|
||||
if (library_path.empty()) {
|
||||
return m_source_module;
|
||||
}
|
||||
memset(lib_wstr, 0, sizeof(lib_wstr));
|
||||
::MultiByteToWideChar(CP_UTF8, NULL, library_path.c_str(), strlen(library_path.c_str()) + 1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0]));
|
||||
m_source_module = LoadLibrary(lib_wstr);
|
||||
}
|
||||
#else
|
||||
#if defined(__WXMAC__)
|
||||
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_SOURCE_LIBRARY) + ".dylib";
|
||||
#else
|
||||
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_SOURCE_LIBRARY) + ".so";
|
||||
#endif
|
||||
m_source_module = dlopen(library.c_str(), RTLD_LAZY);
|
||||
#endif
|
||||
|
||||
return m_source_module;
|
||||
}
|
||||
|
||||
void* BBLNetworkPlugin::get_function(const char* name)
|
||||
{
|
||||
void* function = nullptr;
|
||||
|
||||
if (!m_networking_module)
|
||||
return function;
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
function = GetProcAddress(m_networking_module, name);
|
||||
#else
|
||||
function = dlsym(m_networking_module, name);
|
||||
#endif
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Methods
|
||||
// ============================================================================
|
||||
|
||||
std::string BBLNetworkPlugin::get_libpath_in_current_directory(const std::string& library_name)
|
||||
{
|
||||
std::string lib_path;
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
wchar_t file_name[512];
|
||||
DWORD ret = GetModuleFileNameW(NULL, file_name, 512);
|
||||
if (!ret) {
|
||||
return lib_path;
|
||||
}
|
||||
int size_needed = ::WideCharToMultiByte(0, 0, file_name, wcslen(file_name), nullptr, 0, nullptr, nullptr);
|
||||
std::string file_name_string(size_needed, 0);
|
||||
::WideCharToMultiByte(0, 0, file_name, wcslen(file_name), file_name_string.data(), size_needed, nullptr, nullptr);
|
||||
|
||||
std::size_t found = file_name_string.find("orca-slicer.exe");
|
||||
if (found == (file_name_string.size() - 16)) {
|
||||
lib_path = library_name + ".dll";
|
||||
lib_path = file_name_string.replace(found, 16, lib_path);
|
||||
}
|
||||
#else
|
||||
(void)library_name;
|
||||
#endif
|
||||
return lib_path;
|
||||
}
|
||||
|
||||
std::string BBLNetworkPlugin::get_versioned_library_path(const std::string& version)
|
||||
{
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto plugin_folder = data_dir_path / "plugins";
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
return (plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll")).string();
|
||||
#elif defined(__WXMAC__)
|
||||
return (plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib")).string();
|
||||
#else
|
||||
return (plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so")).string();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BBLNetworkPlugin::versioned_library_exists(const std::string& version)
|
||||
{
|
||||
if (version.empty()) return false;
|
||||
std::string path = get_versioned_library_path(version);
|
||||
|
||||
if (boost::filesystem::exists(path)) return true;
|
||||
|
||||
if (version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) {
|
||||
return legacy_library_exists();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BBLNetworkPlugin::legacy_library_exists()
|
||||
{
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto plugin_folder = data_dir_path / "plugins";
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
auto legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
|
||||
#elif defined(__WXMAC__)
|
||||
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
|
||||
#else
|
||||
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
|
||||
#endif
|
||||
return boost::filesystem::exists(legacy_path);
|
||||
}
|
||||
|
||||
void BBLNetworkPlugin::remove_legacy_library()
|
||||
{
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto plugin_folder = data_dir_path / "plugins";
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
auto legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll");
|
||||
#elif defined(__WXMAC__)
|
||||
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib");
|
||||
#else
|
||||
auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so");
|
||||
#endif
|
||||
|
||||
if (boost::filesystem::exists(legacy_path)) {
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(legacy_path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> BBLNetworkPlugin::scan_plugin_versions()
|
||||
{
|
||||
std::vector<std::string> discovered_versions;
|
||||
std::string data_dir_str = data_dir();
|
||||
boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir_str) / "plugins";
|
||||
|
||||
if (!boost::filesystem::is_directory(plugin_folder)) {
|
||||
return discovered_versions;
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
std::string prefix = std::string(BAMBU_NETWORK_LIBRARY) + "_";
|
||||
std::string extension = ".dll";
|
||||
#elif defined(__WXMAC__)
|
||||
std::string prefix = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_";
|
||||
std::string extension = ".dylib";
|
||||
#else
|
||||
std::string prefix = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_";
|
||||
std::string extension = ".so";
|
||||
#endif
|
||||
|
||||
boost::system::error_code ec;
|
||||
for (auto& entry : boost::filesystem::directory_iterator(plugin_folder, ec)) {
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": error iterating directory: " << ec.message();
|
||||
break;
|
||||
}
|
||||
if (!boost::filesystem::is_regular_file(entry.status()))
|
||||
continue;
|
||||
|
||||
std::string filename = entry.path().filename().string();
|
||||
|
||||
if (filename.rfind(prefix, 0) != 0)
|
||||
continue;
|
||||
if (filename.size() <= extension.size() ||
|
||||
filename.compare(filename.size() - extension.size(), extension.size(), extension) != 0)
|
||||
continue;
|
||||
|
||||
std::string version = filename.substr(prefix.size(),
|
||||
filename.size() - prefix.size() - extension.size());
|
||||
discovered_versions.push_back(version);
|
||||
}
|
||||
|
||||
return discovered_versions;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Handling
|
||||
// ============================================================================
|
||||
|
||||
void BBLNetworkPlugin::clear_load_error()
|
||||
{
|
||||
m_load_error = NetworkLibraryLoadError{};
|
||||
}
|
||||
|
||||
void BBLNetworkPlugin::set_load_error(const std::string& message,
|
||||
const std::string& technical_details,
|
||||
const std::string& attempted_path)
|
||||
{
|
||||
m_load_error.has_error = true;
|
||||
m_load_error.message = message;
|
||||
m_load_error.technical_details = technical_details;
|
||||
m_load_error.attempted_path = attempted_path;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Legacy Helper
|
||||
// ============================================================================
|
||||
|
||||
PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param)
|
||||
{
|
||||
PrintParams_Legacy l;
|
||||
|
||||
l.dev_id = std::move(param.dev_id);
|
||||
l.task_name = std::move(param.task_name);
|
||||
l.project_name = std::move(param.project_name);
|
||||
l.preset_name = std::move(param.preset_name);
|
||||
l.filename = std::move(param.filename);
|
||||
l.config_filename = std::move(param.config_filename);
|
||||
l.plate_index = param.plate_index;
|
||||
l.ftp_folder = std::move(param.ftp_folder);
|
||||
l.ftp_file = std::move(param.ftp_file);
|
||||
l.ftp_file_md5 = std::move(param.ftp_file_md5);
|
||||
l.ams_mapping = std::move(param.ams_mapping);
|
||||
l.ams_mapping_info = std::move(param.ams_mapping_info);
|
||||
l.connection_type = std::move(param.connection_type);
|
||||
l.comments = std::move(param.comments);
|
||||
l.origin_profile_id = param.origin_profile_id;
|
||||
l.stl_design_id = param.stl_design_id;
|
||||
l.origin_model_id = std::move(param.origin_model_id);
|
||||
l.print_type = std::move(param.print_type);
|
||||
l.dst_file = std::move(param.dst_file);
|
||||
l.dev_name = std::move(param.dev_name);
|
||||
l.dev_ip = std::move(param.dev_ip);
|
||||
l.use_ssl_for_ftp = param.use_ssl_for_ftp;
|
||||
l.use_ssl_for_mqtt = param.use_ssl_for_mqtt;
|
||||
l.username = std::move(param.username);
|
||||
l.password = std::move(param.password);
|
||||
l.task_bed_leveling = param.task_bed_leveling;
|
||||
l.task_flow_cali = param.task_flow_cali;
|
||||
l.task_vibration_cali = param.task_vibration_cali;
|
||||
l.task_layer_inspect = param.task_layer_inspect;
|
||||
l.task_record_timelapse = param.task_record_timelapse;
|
||||
l.task_use_ams = param.task_use_ams;
|
||||
l.task_bed_type = std::move(param.task_bed_type);
|
||||
l.extra_options = std::move(param.extra_options);
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Function Pointer Loading
|
||||
// ============================================================================
|
||||
|
||||
void BBLNetworkPlugin::load_all_function_pointers()
|
||||
{
|
||||
m_check_debug_consistent = reinterpret_cast<func_check_debug_consistent>(get_function("bambu_network_check_debug_consistent"));
|
||||
m_get_version = reinterpret_cast<func_get_version>(get_function("bambu_network_get_version"));
|
||||
m_create_agent = reinterpret_cast<func_create_agent>(get_function("bambu_network_create_agent"));
|
||||
m_destroy_agent = reinterpret_cast<func_destroy_agent>(get_function("bambu_network_destroy_agent"));
|
||||
m_init_log = reinterpret_cast<func_init_log>(get_function("bambu_network_init_log"));
|
||||
m_set_config_dir = reinterpret_cast<func_set_config_dir>(get_function("bambu_network_set_config_dir"));
|
||||
m_set_cert_file = reinterpret_cast<func_set_cert_file>(get_function("bambu_network_set_cert_file"));
|
||||
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"));
|
||||
m_set_get_country_code_fn = reinterpret_cast<func_set_get_country_code_fn>(get_function("bambu_network_set_get_country_code_fn"));
|
||||
m_set_on_subscribe_failure_fn = reinterpret_cast<func_set_on_subscribe_failure_fn>(get_function("bambu_network_set_on_subscribe_failure_fn"));
|
||||
m_set_on_message_fn = reinterpret_cast<func_set_on_message_fn>(get_function("bambu_network_set_on_message_fn"));
|
||||
m_set_on_user_message_fn = reinterpret_cast<func_set_on_user_message_fn>(get_function("bambu_network_set_on_user_message_fn"));
|
||||
m_set_on_local_connect_fn = reinterpret_cast<func_set_on_local_connect_fn>(get_function("bambu_network_set_on_local_connect_fn"));
|
||||
m_set_on_local_message_fn = reinterpret_cast<func_set_on_local_message_fn>(get_function("bambu_network_set_on_local_message_fn"));
|
||||
m_set_queue_on_main_fn = reinterpret_cast<func_set_queue_on_main_fn>(get_function("bambu_network_set_queue_on_main_fn"));
|
||||
m_connect_server = reinterpret_cast<func_connect_server>(get_function("bambu_network_connect_server"));
|
||||
m_is_server_connected = reinterpret_cast<func_is_server_connected>(get_function("bambu_network_is_server_connected"));
|
||||
m_refresh_connection = reinterpret_cast<func_refresh_connection>(get_function("bambu_network_refresh_connection"));
|
||||
m_start_subscribe = reinterpret_cast<func_start_subscribe>(get_function("bambu_network_start_subscribe"));
|
||||
m_stop_subscribe = reinterpret_cast<func_stop_subscribe>(get_function("bambu_network_stop_subscribe"));
|
||||
m_add_subscribe = reinterpret_cast<func_add_subscribe>(get_function("bambu_network_add_subscribe"));
|
||||
m_del_subscribe = reinterpret_cast<func_del_subscribe>(get_function("bambu_network_del_subscribe"));
|
||||
m_enable_multi_machine = reinterpret_cast<func_enable_multi_machine>(get_function("bambu_network_enable_multi_machine"));
|
||||
m_send_message = reinterpret_cast<func_send_message>(get_function("bambu_network_send_message"));
|
||||
m_connect_printer = reinterpret_cast<func_connect_printer>(get_function("bambu_network_connect_printer"));
|
||||
m_disconnect_printer = reinterpret_cast<func_disconnect_printer>(get_function("bambu_network_disconnect_printer"));
|
||||
m_send_message_to_printer = reinterpret_cast<func_send_message_to_printer>(get_function("bambu_network_send_message_to_printer"));
|
||||
m_check_cert = reinterpret_cast<func_check_cert>(get_function("bambu_network_update_cert"));
|
||||
m_install_device_cert = reinterpret_cast<func_install_device_cert>(get_function("bambu_network_install_device_cert"));
|
||||
m_start_discovery = reinterpret_cast<func_start_discovery>(get_function("bambu_network_start_discovery"));
|
||||
m_change_user = reinterpret_cast<func_change_user>(get_function("bambu_network_change_user"));
|
||||
m_is_user_login = reinterpret_cast<func_is_user_login>(get_function("bambu_network_is_user_login"));
|
||||
m_user_logout = reinterpret_cast<func_user_logout>(get_function("bambu_network_user_logout"));
|
||||
m_get_user_id = reinterpret_cast<func_get_user_id>(get_function("bambu_network_get_user_id"));
|
||||
m_get_user_name = reinterpret_cast<func_get_user_name>(get_function("bambu_network_get_user_name"));
|
||||
m_get_user_avatar = reinterpret_cast<func_get_user_avatar>(get_function("bambu_network_get_user_avatar"));
|
||||
m_get_user_nickanme = reinterpret_cast<func_get_user_nickanme>(get_function("bambu_network_get_user_nickanme"));
|
||||
m_build_login_cmd = reinterpret_cast<func_build_login_cmd>(get_function("bambu_network_build_login_cmd"));
|
||||
m_build_logout_cmd = reinterpret_cast<func_build_logout_cmd>(get_function("bambu_network_build_logout_cmd"));
|
||||
m_build_login_info = reinterpret_cast<func_build_login_info>(get_function("bambu_network_build_login_info"));
|
||||
m_ping_bind = reinterpret_cast<func_ping_bind>(get_function("bambu_network_ping_bind"));
|
||||
m_bind_detect = reinterpret_cast<func_bind_detect>(get_function("bambu_network_bind_detect"));
|
||||
m_set_server_callback = reinterpret_cast<func_set_server_callback>(get_function("bambu_network_set_server_callback"));
|
||||
m_bind = reinterpret_cast<func_bind>(get_function("bambu_network_bind"));
|
||||
m_unbind = reinterpret_cast<func_unbind>(get_function("bambu_network_unbind"));
|
||||
m_get_bambulab_host = reinterpret_cast<func_get_bambulab_host>(get_function("bambu_network_get_bambulab_host"));
|
||||
m_get_user_selected_machine = reinterpret_cast<func_get_user_selected_machine>(get_function("bambu_network_get_user_selected_machine"));
|
||||
m_set_user_selected_machine = reinterpret_cast<func_set_user_selected_machine>(get_function("bambu_network_set_user_selected_machine"));
|
||||
m_start_print = reinterpret_cast<func_start_print>(get_function("bambu_network_start_print"));
|
||||
m_start_local_print_with_record = reinterpret_cast<func_start_local_print_with_record>(get_function("bambu_network_start_local_print_with_record"));
|
||||
m_start_send_gcode_to_sdcard = reinterpret_cast<func_start_send_gcode_to_sdcard>(get_function("bambu_network_start_send_gcode_to_sdcard"));
|
||||
m_start_local_print = reinterpret_cast<func_start_local_print>(get_function("bambu_network_start_local_print"));
|
||||
m_start_sdcard_print = reinterpret_cast<func_start_sdcard_print>(get_function("bambu_network_start_sdcard_print"));
|
||||
m_get_user_presets = reinterpret_cast<func_get_user_presets>(get_function("bambu_network_get_user_presets"));
|
||||
m_request_setting_id = reinterpret_cast<func_request_setting_id>(get_function("bambu_network_request_setting_id"));
|
||||
m_put_setting = reinterpret_cast<func_put_setting>(get_function("bambu_network_put_setting"));
|
||||
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"));
|
||||
m_get_user_print_info = reinterpret_cast<func_get_user_print_info>(get_function("bambu_network_get_user_print_info"));
|
||||
m_get_user_tasks = reinterpret_cast<func_get_user_tasks>(get_function("bambu_network_get_user_tasks"));
|
||||
m_get_printer_firmware = reinterpret_cast<func_get_printer_firmware>(get_function("bambu_network_get_printer_firmware"));
|
||||
m_get_task_plate_index = reinterpret_cast<func_get_task_plate_index>(get_function("bambu_network_get_task_plate_index"));
|
||||
m_get_user_info = reinterpret_cast<func_get_user_info>(get_function("bambu_network_get_user_info"));
|
||||
m_request_bind_ticket = reinterpret_cast<func_request_bind_ticket>(get_function("bambu_network_request_bind_ticket"));
|
||||
m_get_subtask_info = reinterpret_cast<func_get_subtask_info>(get_function("bambu_network_get_subtask_info"));
|
||||
m_get_slice_info = reinterpret_cast<func_get_slice_info>(get_function("bambu_network_get_slice_info"));
|
||||
m_query_bind_status = reinterpret_cast<func_query_bind_status>(get_function("bambu_network_query_bind_status"));
|
||||
m_modify_printer_name = reinterpret_cast<func_modify_printer_name>(get_function("bambu_network_modify_printer_name"));
|
||||
m_get_camera_url = reinterpret_cast<func_get_camera_url>(get_function("bambu_network_get_camera_url"));
|
||||
m_get_design_staffpick = reinterpret_cast<func_get_design_staffpick>(get_function("bambu_network_get_design_staffpick"));
|
||||
m_start_publish = reinterpret_cast<func_start_pubilsh>(get_function("bambu_network_start_publish"));
|
||||
m_get_model_publish_url = reinterpret_cast<func_get_model_publish_url>(get_function("bambu_network_get_model_publish_url"));
|
||||
m_get_subtask = reinterpret_cast<func_get_subtask>(get_function("bambu_network_get_subtask"));
|
||||
m_get_model_mall_home_url = reinterpret_cast<func_get_model_mall_home_url>(get_function("bambu_network_get_model_mall_home_url"));
|
||||
m_get_model_mall_detail_url = reinterpret_cast<func_get_model_mall_detail_url>(get_function("bambu_network_get_model_mall_detail_url"));
|
||||
m_get_my_profile = reinterpret_cast<func_get_my_profile>(get_function("bambu_network_get_my_profile"));
|
||||
m_track_enable = reinterpret_cast<func_track_enable>(get_function("bambu_network_track_enable"));
|
||||
m_track_remove_files = reinterpret_cast<func_track_remove_files>(get_function("bambu_network_track_remove_files"));
|
||||
m_track_event = reinterpret_cast<func_track_event>(get_function("bambu_network_track_event"));
|
||||
m_track_header = reinterpret_cast<func_track_header>(get_function("bambu_network_track_header"));
|
||||
m_track_update_property = reinterpret_cast<func_track_update_property>(get_function("bambu_network_track_update_property"));
|
||||
m_track_get_property = reinterpret_cast<func_track_get_property>(get_function("bambu_network_track_get_property"));
|
||||
m_put_model_mall_rating = reinterpret_cast<func_put_model_mall_rating_url>(get_function("bambu_network_put_model_mall_rating"));
|
||||
m_get_oss_config = reinterpret_cast<func_get_oss_config>(get_function("bambu_network_get_oss_config"));
|
||||
m_put_rating_picture_oss = reinterpret_cast<func_put_rating_picture_oss>(get_function("bambu_network_put_rating_picture_oss"));
|
||||
m_get_model_mall_rating_result = reinterpret_cast<func_get_model_mall_rating_result>(get_function("bambu_network_get_model_mall_rating"));
|
||||
m_get_mw_user_preference = reinterpret_cast<func_get_mw_user_preference>(get_function("bambu_network_get_mw_user_preference"));
|
||||
m_get_mw_user_4ulist = reinterpret_cast<func_get_mw_user_4ulist>(get_function("bambu_network_get_mw_user_4ulist"));
|
||||
}
|
||||
|
||||
void BBLNetworkPlugin::clear_all_function_pointers()
|
||||
{
|
||||
m_check_debug_consistent = nullptr;
|
||||
m_get_version = nullptr;
|
||||
m_create_agent = nullptr;
|
||||
m_destroy_agent = nullptr;
|
||||
m_init_log = nullptr;
|
||||
m_set_config_dir = nullptr;
|
||||
m_set_cert_file = nullptr;
|
||||
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;
|
||||
m_set_get_country_code_fn = nullptr;
|
||||
m_set_on_subscribe_failure_fn = nullptr;
|
||||
m_set_on_message_fn = nullptr;
|
||||
m_set_on_user_message_fn = nullptr;
|
||||
m_set_on_local_connect_fn = nullptr;
|
||||
m_set_on_local_message_fn = nullptr;
|
||||
m_set_queue_on_main_fn = nullptr;
|
||||
m_connect_server = nullptr;
|
||||
m_is_server_connected = nullptr;
|
||||
m_refresh_connection = nullptr;
|
||||
m_start_subscribe = nullptr;
|
||||
m_stop_subscribe = nullptr;
|
||||
m_add_subscribe = nullptr;
|
||||
m_del_subscribe = nullptr;
|
||||
m_enable_multi_machine = nullptr;
|
||||
m_send_message = nullptr;
|
||||
m_connect_printer = nullptr;
|
||||
m_disconnect_printer = nullptr;
|
||||
m_send_message_to_printer = nullptr;
|
||||
m_check_cert = nullptr;
|
||||
m_install_device_cert = nullptr;
|
||||
m_start_discovery = nullptr;
|
||||
m_change_user = nullptr;
|
||||
m_is_user_login = nullptr;
|
||||
m_user_logout = nullptr;
|
||||
m_get_user_id = nullptr;
|
||||
m_get_user_name = nullptr;
|
||||
m_get_user_avatar = nullptr;
|
||||
m_get_user_nickanme = nullptr;
|
||||
m_build_login_cmd = nullptr;
|
||||
m_build_logout_cmd = nullptr;
|
||||
m_build_login_info = nullptr;
|
||||
m_ping_bind = nullptr;
|
||||
m_bind_detect = nullptr;
|
||||
m_set_server_callback = nullptr;
|
||||
m_bind = nullptr;
|
||||
m_unbind = nullptr;
|
||||
m_get_bambulab_host = nullptr;
|
||||
m_get_user_selected_machine = nullptr;
|
||||
m_set_user_selected_machine = nullptr;
|
||||
m_start_print = nullptr;
|
||||
m_start_local_print_with_record = nullptr;
|
||||
m_start_send_gcode_to_sdcard = nullptr;
|
||||
m_start_local_print = nullptr;
|
||||
m_start_sdcard_print = nullptr;
|
||||
m_get_user_presets = nullptr;
|
||||
m_request_setting_id = nullptr;
|
||||
m_put_setting = nullptr;
|
||||
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;
|
||||
m_get_user_print_info = nullptr;
|
||||
m_get_user_tasks = nullptr;
|
||||
m_get_printer_firmware = nullptr;
|
||||
m_get_task_plate_index = nullptr;
|
||||
m_get_user_info = nullptr;
|
||||
m_request_bind_ticket = nullptr;
|
||||
m_get_subtask_info = nullptr;
|
||||
m_get_slice_info = nullptr;
|
||||
m_query_bind_status = nullptr;
|
||||
m_modify_printer_name = nullptr;
|
||||
m_get_camera_url = nullptr;
|
||||
m_get_design_staffpick = nullptr;
|
||||
m_start_publish = nullptr;
|
||||
m_get_model_publish_url = nullptr;
|
||||
m_get_subtask = nullptr;
|
||||
m_get_model_mall_home_url = nullptr;
|
||||
m_get_model_mall_detail_url = nullptr;
|
||||
m_get_my_profile = nullptr;
|
||||
m_track_enable = nullptr;
|
||||
m_track_remove_files = nullptr;
|
||||
m_track_event = nullptr;
|
||||
m_track_header = nullptr;
|
||||
m_track_update_property = nullptr;
|
||||
m_track_get_property = nullptr;
|
||||
m_put_model_mall_rating = nullptr;
|
||||
m_get_oss_config = nullptr;
|
||||
m_put_rating_picture_oss = nullptr;
|
||||
m_get_model_mall_rating_result = nullptr;
|
||||
m_get_mw_user_preference = nullptr;
|
||||
m_get_mw_user_4ulist = nullptr;
|
||||
}
|
||||
|
||||
std::vector<NetworkLibraryVersionInfo> get_all_available_versions()
|
||||
{
|
||||
std::vector<NetworkLibraryVersionInfo> result;
|
||||
std::set<std::string> known_base_versions;
|
||||
std::set<std::string> all_known_versions;
|
||||
|
||||
for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) {
|
||||
result.push_back(NetworkLibraryVersionInfo::from_static(AVAILABLE_NETWORK_VERSIONS[i]));
|
||||
known_base_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version);
|
||||
all_known_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version);
|
||||
}
|
||||
|
||||
std::vector<std::string> discovered = BBLNetworkPlugin::scan_plugin_versions();
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> suffixed_versions;
|
||||
|
||||
for (const auto& version : discovered) {
|
||||
if (all_known_versions.count(version) > 0)
|
||||
continue;
|
||||
|
||||
std::string base = extract_base_version(version);
|
||||
std::string suffix = extract_suffix(version);
|
||||
|
||||
if (suffix.empty())
|
||||
continue;
|
||||
|
||||
if (known_base_versions.count(base) == 0)
|
||||
continue;
|
||||
|
||||
suffixed_versions.emplace_back(base, version);
|
||||
all_known_versions.insert(version);
|
||||
}
|
||||
|
||||
std::sort(suffixed_versions.begin(), suffixed_versions.end(),
|
||||
[](const auto& a, const auto& b) {
|
||||
if (a.first != b.first) return a.first > b.first;
|
||||
return a.second < b.second;
|
||||
});
|
||||
|
||||
for (const auto& [base, full] : suffixed_versions) {
|
||||
size_t insert_pos = 0;
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
if (result[i].base_version == base) {
|
||||
insert_pos = i + 1;
|
||||
while (insert_pos < result.size() &&
|
||||
result[insert_pos].base_version == base) {
|
||||
++insert_pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string sfx = extract_suffix(full);
|
||||
result.insert(result.begin() + insert_pos,
|
||||
NetworkLibraryVersionInfo::from_discovered(full, base, sfx));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,515 @@
|
||||
#ifndef __BBL_NETWORK_PLUGIN_HPP__
|
||||
#define __BBL_NETWORK_PLUGIN_HPP__
|
||||
|
||||
#include "bambu_networking.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// ============================================================================
|
||||
// Function Pointer Types (copied from NetworkAgent.hpp)
|
||||
// ============================================================================
|
||||
|
||||
typedef bool (*func_check_debug_consistent)(bool is_debug);
|
||||
typedef std::string (*func_get_version)(void);
|
||||
typedef void* (*func_create_agent)(std::string log_dir);
|
||||
typedef int (*func_destroy_agent)(void *agent);
|
||||
typedef int (*func_init_log)(void *agent);
|
||||
typedef int (*func_set_config_dir)(void *agent, std::string config_dir);
|
||||
typedef int (*func_set_cert_file)(void *agent, std::string folder, std::string filename);
|
||||
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);
|
||||
typedef int (*func_set_get_country_code_fn)(void *agent, GetCountryCodeFn fn);
|
||||
typedef int (*func_set_on_subscribe_failure_fn)(void *agent, GetSubscribeFailureFn fn);
|
||||
typedef int (*func_set_on_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_on_user_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_on_local_connect_fn)(void *agent, OnLocalConnectedFn fn);
|
||||
typedef int (*func_set_on_local_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_queue_on_main_fn)(void *agent, QueueOnMainFn fn);
|
||||
typedef int (*func_connect_server)(void *agent);
|
||||
typedef bool (*func_is_server_connected)(void *agent);
|
||||
typedef int (*func_refresh_connection)(void *agent);
|
||||
typedef int (*func_start_subscribe)(void *agent, std::string module);
|
||||
typedef int (*func_stop_subscribe)(void *agent, std::string module);
|
||||
typedef int (*func_add_subscribe)(void *agent, std::vector<std::string> dev_list);
|
||||
typedef int (*func_del_subscribe)(void *agent, std::vector<std::string> dev_list);
|
||||
typedef void (*func_enable_multi_machine)(void *agent, bool enable);
|
||||
typedef int (*func_send_message)(void *agent, std::string dev_id, std::string json_str, int qos, int flag);
|
||||
typedef int (*func_connect_printer)(void *agent, std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
typedef int (*func_disconnect_printer)(void *agent);
|
||||
typedef int (*func_send_message_to_printer)(void *agent, std::string dev_id, std::string json_str, int qos, int flag);
|
||||
typedef int (*func_check_cert)(void* agent);
|
||||
typedef void (*func_install_device_cert)(void* agent, std::string dev_id, bool lan_only);
|
||||
typedef bool (*func_start_discovery)(void *agent, bool start, bool sending);
|
||||
typedef int (*func_change_user)(void *agent, std::string user_info);
|
||||
typedef bool (*func_is_user_login)(void *agent);
|
||||
typedef int (*func_user_logout)(void *agent, bool request);
|
||||
typedef std::string (*func_get_user_id)(void *agent);
|
||||
typedef std::string (*func_get_user_name)(void *agent);
|
||||
typedef std::string (*func_get_user_avatar)(void *agent);
|
||||
typedef std::string (*func_get_user_nickanme)(void *agent);
|
||||
typedef std::string (*func_build_login_cmd)(void *agent);
|
||||
typedef std::string (*func_build_logout_cmd)(void *agent);
|
||||
typedef std::string (*func_build_login_info)(void *agent);
|
||||
typedef int (*func_ping_bind)(void *agent, std::string ping_code);
|
||||
typedef int (*func_bind_detect)(void *agent, std::string dev_ip, std::string sec_link, detectResult& detect);
|
||||
typedef int (*func_set_server_callback)(void *agent, OnServerErrFn fn);
|
||||
typedef int (*func_bind)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
|
||||
typedef int (*func_unbind)(void *agent, std::string dev_id);
|
||||
typedef std::string (*func_get_bambulab_host)(void *agent);
|
||||
typedef std::string (*func_get_user_selected_machine)(void *agent);
|
||||
typedef int (*func_set_user_selected_machine)(void *agent, std::string dev_id);
|
||||
typedef int (*func_start_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print_with_record)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_send_gcode_to_sdcard)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_start_sdcard_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_get_user_presets)(void *agent, std::map<std::string, std::map<std::string, std::string>>* user_presets);
|
||||
typedef std::string (*func_request_setting_id)(void *agent, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
typedef int (*func_put_setting)(void *agent, std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
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);
|
||||
typedef int (*func_get_user_print_info)(void *agent, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_get_user_tasks)(void *agent, TaskQueryParams params, std::string* http_body);
|
||||
typedef int (*func_get_printer_firmware)(void *agent, std::string dev_id, unsigned* http_code, std::string* http_body);
|
||||
typedef int (*func_get_task_plate_index)(void *agent, std::string task_id, int* plate_index);
|
||||
typedef int (*func_get_user_info)(void *agent, int* identifier);
|
||||
typedef int (*func_request_bind_ticket)(void *agent, std::string* ticket);
|
||||
typedef int (*func_get_subtask_info)(void *agent, std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string *http_body);
|
||||
typedef int (*func_get_slice_info)(void *agent, std::string project_id, std::string profile_id, int plate_index, std::string* slice_json);
|
||||
typedef int (*func_query_bind_status)(void *agent, std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_modify_printer_name)(void *agent, std::string dev_id, std::string dev_name);
|
||||
typedef int (*func_get_camera_url)(void *agent, std::string dev_id, std::function<void(std::string)> callback);
|
||||
typedef int (*func_get_design_staffpick)(void *agent, int offset, int limit, std::function<void(std::string)> callback);
|
||||
typedef int (*func_start_pubilsh)(void *agent, PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out);
|
||||
typedef int (*func_get_model_publish_url)(void *agent, std::string* url);
|
||||
typedef int (*func_get_subtask)(void *agent, BBLModelTask* task, OnGetSubTaskFn getsub_fn);
|
||||
typedef int (*func_get_model_mall_home_url)(void *agent, std::string* url);
|
||||
typedef int (*func_get_model_mall_detail_url)(void *agent, std::string* url, std::string id);
|
||||
typedef int (*func_get_my_profile)(void *agent, std::string token, unsigned int *http_code, std::string *http_body);
|
||||
typedef int (*func_track_enable)(void *agent, bool enable);
|
||||
typedef int (*func_track_remove_files)(void *agent);
|
||||
typedef int (*func_track_event)(void *agent, std::string evt_key, std::string content);
|
||||
typedef int (*func_track_header)(void *agent, std::string header);
|
||||
typedef int (*func_track_update_property)(void *agent, std::string name, std::string value, std::string type);
|
||||
typedef int (*func_track_get_property)(void *agent, std::string name, std::string& value, std::string type);
|
||||
typedef int (*func_put_model_mall_rating_url)(void *agent, int rating_id, int score, std::string content, std::vector<std::string> images, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_oss_config)(void *agent, std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_put_rating_picture_oss)(void *agent, std::string &config, std::string &pic_oss_path, std::string model_id, int profile_id, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_model_mall_rating_result)(void *agent, int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_mw_user_preference)(void *agent, std::function<void(std::string)> callback);
|
||||
typedef int (*func_get_mw_user_4ulist)(void *agent, int seed, int limit, std::function<void(std::string)> callback);
|
||||
|
||||
// Legacy function pointer types (for older DLL versions)
|
||||
typedef int (*func_start_print_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print_with_record_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_send_gcode_to_sdcard_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_start_sdcard_print_legacy)(void* agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_send_message_legacy)(void* agent, std::string dev_id, std::string json_str, int qos);
|
||||
typedef int (*func_send_message_to_printer_legacy)(void* agent, std::string dev_id, std::string json_str, int qos);
|
||||
|
||||
/**
|
||||
* BBLNetworkPlugin - Singleton managing the Bambu Lab network DLL.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Owns the DLL module handle (netwoking_module)
|
||||
* - Owns the DLL source module handle (source_module)
|
||||
* - Manages the shared void* agent handle
|
||||
* - Provides all function pointers to BBL agents
|
||||
*
|
||||
* Usage:
|
||||
* auto& plugin = BBLNetworkPlugin::instance();
|
||||
* if (plugin.initialize(version)) {
|
||||
* plugin.create_agent(log_dir);
|
||||
* // Now BBLCloudServiceAgent/BBLPrinterAgent can use plugin
|
||||
* }
|
||||
*/
|
||||
class BBLNetworkPlugin {
|
||||
public:
|
||||
// Singleton access
|
||||
static BBLNetworkPlugin& instance();
|
||||
|
||||
// Delete copy/move
|
||||
BBLNetworkPlugin(const BBLNetworkPlugin&) = delete;
|
||||
BBLNetworkPlugin& operator=(const BBLNetworkPlugin&) = delete;
|
||||
BBLNetworkPlugin(BBLNetworkPlugin&&) = delete;
|
||||
BBLNetworkPlugin& operator=(BBLNetworkPlugin&&) = delete;
|
||||
|
||||
// ========================================================================
|
||||
// Module Lifecycle
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Load the network DLL from the plugins folder.
|
||||
* @param using_backup If true, look in plugins/backup folder
|
||||
* @param version Required version string (e.g., "01.09.05.01")
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int initialize(bool using_backup = false, const std::string& version = "");
|
||||
|
||||
/**
|
||||
* Unload the network DLL and clear all function pointers.
|
||||
* @return 0 on success
|
||||
*/
|
||||
int unload();
|
||||
|
||||
/**
|
||||
* Destroy the singleton instance.
|
||||
* Safe to call multiple times - does nothing if already destroyed.
|
||||
* Must be called during application shutdown before main() returns.
|
||||
*/
|
||||
static void shutdown();
|
||||
|
||||
/**
|
||||
* Check if DLL is currently loaded.
|
||||
*/
|
||||
bool is_loaded() const;
|
||||
|
||||
/**
|
||||
* Get the plugin version string.
|
||||
*/
|
||||
std::string get_version() const;
|
||||
|
||||
// ========================================================================
|
||||
// Agent Lifecycle
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Create the shared agent handle.
|
||||
* Only one agent can exist at a time.
|
||||
* @param log_dir Directory for log files
|
||||
* @return The created agent handle, or nullptr on failure
|
||||
*/
|
||||
void* create_agent(const std::string& log_dir);
|
||||
|
||||
/**
|
||||
* Destroy the shared agent handle.
|
||||
* @return 0 on success
|
||||
*/
|
||||
int destroy_agent();
|
||||
|
||||
/**
|
||||
* Get the current agent handle.
|
||||
* Returns nullptr if no agent created.
|
||||
*/
|
||||
void* get_agent() const { return m_agent; }
|
||||
|
||||
/**
|
||||
* Check if an agent has been created.
|
||||
*/
|
||||
bool has_agent() const { return m_agent != nullptr; }
|
||||
|
||||
// ========================================================================
|
||||
// DLL Module Accessors
|
||||
// ========================================================================
|
||||
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
HMODULE get_networking_module() const { return m_networking_module; }
|
||||
HMODULE get_source_module();
|
||||
#else
|
||||
void* get_networking_module() const { return m_networking_module; }
|
||||
void* get_source_module();
|
||||
#endif
|
||||
|
||||
void* get_function(const char* name);
|
||||
|
||||
// Aliases for backward compatibility with NetworkAgent API
|
||||
void* get_network_function(const char* name) { return get_function(name); }
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
HMODULE get_bambu_source_entry() { return get_source_module(); }
|
||||
#else
|
||||
void* get_bambu_source_entry() { return get_source_module(); }
|
||||
#endif
|
||||
|
||||
// ========================================================================
|
||||
// Utility Methods
|
||||
// ========================================================================
|
||||
|
||||
static std::string get_libpath_in_current_directory(const std::string& library_name);
|
||||
static std::string get_versioned_library_path(const std::string& version);
|
||||
static bool versioned_library_exists(const std::string& version);
|
||||
static bool legacy_library_exists();
|
||||
static void remove_legacy_library();
|
||||
static std::vector<std::string> scan_plugin_versions();
|
||||
|
||||
// ========================================================================
|
||||
// Error Handling
|
||||
// ========================================================================
|
||||
|
||||
NetworkLibraryLoadError get_load_error() const { return m_load_error; }
|
||||
void clear_load_error();
|
||||
void set_load_error(const std::string& message,
|
||||
const std::string& technical_details,
|
||||
const std::string& attempted_path);
|
||||
|
||||
// ========================================================================
|
||||
// Legacy Network Flag
|
||||
// ========================================================================
|
||||
|
||||
bool use_legacy_network() const { return m_use_legacy_network; }
|
||||
void set_use_legacy_network(bool legacy) { m_use_legacy_network = legacy; }
|
||||
|
||||
// ========================================================================
|
||||
// Function Pointer Accessors
|
||||
// ========================================================================
|
||||
|
||||
func_check_debug_consistent get_check_debug_consistent() const { return m_check_debug_consistent; }
|
||||
func_get_version get_get_version() const { return m_get_version; }
|
||||
func_create_agent get_create_agent() const { return m_create_agent; }
|
||||
func_destroy_agent get_destroy_agent() const { return m_destroy_agent; }
|
||||
func_init_log get_init_log() const { return m_init_log; }
|
||||
func_set_config_dir get_set_config_dir() const { return m_set_config_dir; }
|
||||
func_set_cert_file get_set_cert_file() const { return m_set_cert_file; }
|
||||
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; }
|
||||
func_set_get_country_code_fn get_set_get_country_code_fn() const { return m_set_get_country_code_fn; }
|
||||
func_set_on_subscribe_failure_fn get_set_on_subscribe_failure_fn() const { return m_set_on_subscribe_failure_fn; }
|
||||
func_set_on_message_fn get_set_on_message_fn() const { return m_set_on_message_fn; }
|
||||
func_set_on_user_message_fn get_set_on_user_message_fn() const { return m_set_on_user_message_fn; }
|
||||
func_set_on_local_connect_fn get_set_on_local_connect_fn() const { return m_set_on_local_connect_fn; }
|
||||
func_set_on_local_message_fn get_set_on_local_message_fn() const { return m_set_on_local_message_fn; }
|
||||
func_set_queue_on_main_fn get_set_queue_on_main_fn() const { return m_set_queue_on_main_fn; }
|
||||
func_connect_server get_connect_server() const { return m_connect_server; }
|
||||
func_is_server_connected get_is_server_connected() const { return m_is_server_connected; }
|
||||
func_refresh_connection get_refresh_connection() const { return m_refresh_connection; }
|
||||
func_start_subscribe get_start_subscribe() const { return m_start_subscribe; }
|
||||
func_stop_subscribe get_stop_subscribe() const { return m_stop_subscribe; }
|
||||
func_add_subscribe get_add_subscribe() const { return m_add_subscribe; }
|
||||
func_del_subscribe get_del_subscribe() const { return m_del_subscribe; }
|
||||
func_enable_multi_machine get_enable_multi_machine() const { return m_enable_multi_machine; }
|
||||
func_send_message get_send_message() const { return m_send_message; }
|
||||
func_connect_printer get_connect_printer() const { return m_connect_printer; }
|
||||
func_disconnect_printer get_disconnect_printer() const { return m_disconnect_printer; }
|
||||
func_send_message_to_printer get_send_message_to_printer() const { return m_send_message_to_printer; }
|
||||
func_check_cert get_check_cert() const { return m_check_cert; }
|
||||
func_install_device_cert get_install_device_cert() const { return m_install_device_cert; }
|
||||
func_start_discovery get_start_discovery() const { return m_start_discovery; }
|
||||
func_change_user get_change_user() const { return m_change_user; }
|
||||
func_is_user_login get_is_user_login() const { return m_is_user_login; }
|
||||
func_user_logout get_user_logout() const { return m_user_logout; }
|
||||
func_get_user_id get_get_user_id() const { return m_get_user_id; }
|
||||
func_get_user_name get_get_user_name() const { return m_get_user_name; }
|
||||
func_get_user_avatar get_get_user_avatar() const { return m_get_user_avatar; }
|
||||
func_get_user_nickanme get_get_user_nickanme() const { return m_get_user_nickanme; }
|
||||
func_build_login_cmd get_build_login_cmd() const { return m_build_login_cmd; }
|
||||
func_build_logout_cmd get_build_logout_cmd() const { return m_build_logout_cmd; }
|
||||
func_build_login_info get_build_login_info() const { return m_build_login_info; }
|
||||
func_ping_bind get_ping_bind() const { return m_ping_bind; }
|
||||
func_bind_detect get_bind_detect() const { return m_bind_detect; }
|
||||
func_set_server_callback get_set_server_callback() const { return m_set_server_callback; }
|
||||
func_bind get_bind() const { return m_bind; }
|
||||
func_unbind get_unbind() const { return m_unbind; }
|
||||
func_get_bambulab_host get_get_bambulab_host() const { return m_get_bambulab_host; }
|
||||
func_get_user_selected_machine get_get_user_selected_machine() const { return m_get_user_selected_machine; }
|
||||
func_set_user_selected_machine get_set_user_selected_machine() const { return m_set_user_selected_machine; }
|
||||
func_start_print get_start_print() const { return m_start_print; }
|
||||
func_start_local_print_with_record get_start_local_print_with_record() const { return m_start_local_print_with_record; }
|
||||
func_start_send_gcode_to_sdcard get_start_send_gcode_to_sdcard() const { return m_start_send_gcode_to_sdcard; }
|
||||
func_start_local_print get_start_local_print() const { return m_start_local_print; }
|
||||
func_start_sdcard_print get_start_sdcard_print() const { return m_start_sdcard_print; }
|
||||
func_get_user_presets get_get_user_presets() const { return m_get_user_presets; }
|
||||
func_request_setting_id get_request_setting_id() const { return m_request_setting_id; }
|
||||
func_put_setting get_put_setting() const { return m_put_setting; }
|
||||
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; }
|
||||
func_get_user_print_info get_get_user_print_info() const { return m_get_user_print_info; }
|
||||
func_get_user_tasks get_get_user_tasks() const { return m_get_user_tasks; }
|
||||
func_get_printer_firmware get_get_printer_firmware() const { return m_get_printer_firmware; }
|
||||
func_get_task_plate_index get_get_task_plate_index() const { return m_get_task_plate_index; }
|
||||
func_get_user_info get_get_user_info() const { return m_get_user_info; }
|
||||
func_request_bind_ticket get_request_bind_ticket() const { return m_request_bind_ticket; }
|
||||
func_get_subtask_info get_get_subtask_info() const { return m_get_subtask_info; }
|
||||
func_get_slice_info get_get_slice_info() const { return m_get_slice_info; }
|
||||
func_query_bind_status get_query_bind_status() const { return m_query_bind_status; }
|
||||
func_modify_printer_name get_modify_printer_name() const { return m_modify_printer_name; }
|
||||
func_get_camera_url get_get_camera_url() const { return m_get_camera_url; }
|
||||
func_get_design_staffpick get_get_design_staffpick() const { return m_get_design_staffpick; }
|
||||
func_start_pubilsh get_start_publish() const { return m_start_publish; }
|
||||
func_get_model_publish_url get_get_model_publish_url() const { return m_get_model_publish_url; }
|
||||
func_get_subtask get_get_subtask() const { return m_get_subtask; }
|
||||
func_get_model_mall_home_url get_get_model_mall_home_url() const { return m_get_model_mall_home_url; }
|
||||
func_get_model_mall_detail_url get_get_model_mall_detail_url() const { return m_get_model_mall_detail_url; }
|
||||
func_get_my_profile get_get_my_profile() const { return m_get_my_profile; }
|
||||
func_track_enable get_track_enable() const { return m_track_enable; }
|
||||
func_track_remove_files get_track_remove_files() const { return m_track_remove_files; }
|
||||
func_track_event get_track_event() const { return m_track_event; }
|
||||
func_track_header get_track_header() const { return m_track_header; }
|
||||
func_track_update_property get_track_update_property() const { return m_track_update_property; }
|
||||
func_track_get_property get_track_get_property() const { return m_track_get_property; }
|
||||
func_put_model_mall_rating_url get_put_model_mall_rating() const { return m_put_model_mall_rating; }
|
||||
func_get_oss_config get_get_oss_config() const { return m_get_oss_config; }
|
||||
func_put_rating_picture_oss get_put_rating_picture_oss() const { return m_put_rating_picture_oss; }
|
||||
func_get_model_mall_rating_result get_get_model_mall_rating_result() const { return m_get_model_mall_rating_result; }
|
||||
func_get_mw_user_preference get_get_mw_user_preference() const { return m_get_mw_user_preference; }
|
||||
func_get_mw_user_4ulist get_get_mw_user_4ulist() const { return m_get_mw_user_4ulist; }
|
||||
|
||||
// ========================================================================
|
||||
// Legacy Helper
|
||||
// ========================================================================
|
||||
|
||||
static PrintParams_Legacy as_legacy(PrintParams& param);
|
||||
|
||||
private:
|
||||
// Singleton instance pointer (heap-allocated for explicit lifetime control)
|
||||
static BBLNetworkPlugin* s_instance;
|
||||
|
||||
BBLNetworkPlugin();
|
||||
~BBLNetworkPlugin();
|
||||
|
||||
void load_all_function_pointers();
|
||||
void clear_all_function_pointers();
|
||||
|
||||
// Module handles
|
||||
#if defined(_MSC_VER) || defined(_WIN32)
|
||||
HMODULE m_networking_module{nullptr};
|
||||
HMODULE m_source_module{nullptr};
|
||||
#else
|
||||
void* m_networking_module{nullptr};
|
||||
void* m_source_module{nullptr};
|
||||
#endif
|
||||
|
||||
// Shared agent handle
|
||||
void* m_agent{nullptr};
|
||||
|
||||
// Load error state
|
||||
NetworkLibraryLoadError m_load_error;
|
||||
|
||||
// Legacy network compatibility flag
|
||||
bool m_use_legacy_network{true};
|
||||
|
||||
// Function pointers
|
||||
func_check_debug_consistent m_check_debug_consistent{nullptr};
|
||||
func_get_version m_get_version{nullptr};
|
||||
func_create_agent m_create_agent{nullptr};
|
||||
func_destroy_agent m_destroy_agent{nullptr};
|
||||
func_init_log m_init_log{nullptr};
|
||||
func_set_config_dir m_set_config_dir{nullptr};
|
||||
func_set_cert_file m_set_cert_file{nullptr};
|
||||
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};
|
||||
func_set_get_country_code_fn m_set_get_country_code_fn{nullptr};
|
||||
func_set_on_subscribe_failure_fn m_set_on_subscribe_failure_fn{nullptr};
|
||||
func_set_on_message_fn m_set_on_message_fn{nullptr};
|
||||
func_set_on_user_message_fn m_set_on_user_message_fn{nullptr};
|
||||
func_set_on_local_connect_fn m_set_on_local_connect_fn{nullptr};
|
||||
func_set_on_local_message_fn m_set_on_local_message_fn{nullptr};
|
||||
func_set_queue_on_main_fn m_set_queue_on_main_fn{nullptr};
|
||||
func_connect_server m_connect_server{nullptr};
|
||||
func_is_server_connected m_is_server_connected{nullptr};
|
||||
func_refresh_connection m_refresh_connection{nullptr};
|
||||
func_start_subscribe m_start_subscribe{nullptr};
|
||||
func_stop_subscribe m_stop_subscribe{nullptr};
|
||||
func_add_subscribe m_add_subscribe{nullptr};
|
||||
func_del_subscribe m_del_subscribe{nullptr};
|
||||
func_enable_multi_machine m_enable_multi_machine{nullptr};
|
||||
func_send_message m_send_message{nullptr};
|
||||
func_connect_printer m_connect_printer{nullptr};
|
||||
func_disconnect_printer m_disconnect_printer{nullptr};
|
||||
func_send_message_to_printer m_send_message_to_printer{nullptr};
|
||||
func_check_cert m_check_cert{nullptr};
|
||||
func_install_device_cert m_install_device_cert{nullptr};
|
||||
func_start_discovery m_start_discovery{nullptr};
|
||||
func_change_user m_change_user{nullptr};
|
||||
func_is_user_login m_is_user_login{nullptr};
|
||||
func_user_logout m_user_logout{nullptr};
|
||||
func_get_user_id m_get_user_id{nullptr};
|
||||
func_get_user_name m_get_user_name{nullptr};
|
||||
func_get_user_avatar m_get_user_avatar{nullptr};
|
||||
func_get_user_nickanme m_get_user_nickanme{nullptr};
|
||||
func_build_login_cmd m_build_login_cmd{nullptr};
|
||||
func_build_logout_cmd m_build_logout_cmd{nullptr};
|
||||
func_build_login_info m_build_login_info{nullptr};
|
||||
func_ping_bind m_ping_bind{nullptr};
|
||||
func_bind_detect m_bind_detect{nullptr};
|
||||
func_set_server_callback m_set_server_callback{nullptr};
|
||||
func_bind m_bind{nullptr};
|
||||
func_unbind m_unbind{nullptr};
|
||||
func_get_bambulab_host m_get_bambulab_host{nullptr};
|
||||
func_get_user_selected_machine m_get_user_selected_machine{nullptr};
|
||||
func_set_user_selected_machine m_set_user_selected_machine{nullptr};
|
||||
func_start_print m_start_print{nullptr};
|
||||
func_start_local_print_with_record m_start_local_print_with_record{nullptr};
|
||||
func_start_send_gcode_to_sdcard m_start_send_gcode_to_sdcard{nullptr};
|
||||
func_start_local_print m_start_local_print{nullptr};
|
||||
func_start_sdcard_print m_start_sdcard_print{nullptr};
|
||||
func_get_user_presets m_get_user_presets{nullptr};
|
||||
func_request_setting_id m_request_setting_id{nullptr};
|
||||
func_put_setting m_put_setting{nullptr};
|
||||
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};
|
||||
func_get_user_print_info m_get_user_print_info{nullptr};
|
||||
func_get_user_tasks m_get_user_tasks{nullptr};
|
||||
func_get_printer_firmware m_get_printer_firmware{nullptr};
|
||||
func_get_task_plate_index m_get_task_plate_index{nullptr};
|
||||
func_get_user_info m_get_user_info{nullptr};
|
||||
func_request_bind_ticket m_request_bind_ticket{nullptr};
|
||||
func_get_subtask_info m_get_subtask_info{nullptr};
|
||||
func_get_slice_info m_get_slice_info{nullptr};
|
||||
func_query_bind_status m_query_bind_status{nullptr};
|
||||
func_modify_printer_name m_modify_printer_name{nullptr};
|
||||
func_get_camera_url m_get_camera_url{nullptr};
|
||||
func_get_design_staffpick m_get_design_staffpick{nullptr};
|
||||
func_start_pubilsh m_start_publish{nullptr};
|
||||
func_get_model_publish_url m_get_model_publish_url{nullptr};
|
||||
func_get_subtask m_get_subtask{nullptr};
|
||||
func_get_model_mall_home_url m_get_model_mall_home_url{nullptr};
|
||||
func_get_model_mall_detail_url m_get_model_mall_detail_url{nullptr};
|
||||
func_get_my_profile m_get_my_profile{nullptr};
|
||||
func_track_enable m_track_enable{nullptr};
|
||||
func_track_remove_files m_track_remove_files{nullptr};
|
||||
func_track_event m_track_event{nullptr};
|
||||
func_track_header m_track_header{nullptr};
|
||||
func_track_update_property m_track_update_property{nullptr};
|
||||
func_track_get_property m_track_get_property{nullptr};
|
||||
func_put_model_mall_rating_url m_put_model_mall_rating{nullptr};
|
||||
func_get_oss_config m_get_oss_config{nullptr};
|
||||
func_put_rating_picture_oss m_put_rating_picture_oss{nullptr};
|
||||
func_get_model_mall_rating_result m_get_model_mall_rating_result{nullptr};
|
||||
func_get_mw_user_preference m_get_mw_user_preference{nullptr};
|
||||
func_get_mw_user_4ulist m_get_mw_user_4ulist{nullptr};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __BBL_NETWORK_PLUGIN_HPP__
|
||||
@@ -0,0 +1,372 @@
|
||||
#include "BBLPrinterAgent.hpp"
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BBLPrinterAgent::BBLPrinterAgent() = default;
|
||||
|
||||
BBLPrinterAgent::~BBLPrinterAgent() = default;
|
||||
|
||||
void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
|
||||
{
|
||||
m_cloud_agent = cloud;
|
||||
// BBL DLL manages tokens internally, so this is just for interface compliance
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Communication
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_send_message();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, json_str, qos, flag);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_connect_printer();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, dev_ip, username, password, use_ssl);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::disconnect_printer()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_disconnect_printer();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_send_message_to_printer();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id, json_str, qos, flag);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Certificates
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::check_cert()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_check_cert();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void BBLPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_install_device_cert();
|
||||
if (func && agent) {
|
||||
func(agent, dev_id, lan_only);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Discovery
|
||||
// ============================================================================
|
||||
|
||||
bool BBLPrinterAgent::start_discovery(bool start, bool sending)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_discovery();
|
||||
if (func && agent) {
|
||||
return func(agent, start, sending);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Binding
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::ping_bind(std::string ping_code)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_ping_bind();
|
||||
if (func && agent) {
|
||||
return func(agent, ping_code);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_bind_detect();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_ip, sec_link, detect);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_bind();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_ip, dev_id, sec_link, timezone, improved, update_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::unbind(std::string dev_id)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_unbind();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_request_bind_ticket();
|
||||
if (func && agent) {
|
||||
return func(agent, ticket);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_server_callback();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Machine Selection
|
||||
// ============================================================================
|
||||
|
||||
std::string BBLPrinterAgent::get_user_selected_machine()
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_get_user_selected_machine();
|
||||
if (func && agent) {
|
||||
return func(agent);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_user_selected_machine();
|
||||
if (func && agent) {
|
||||
return func(agent, dev_id);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Information
|
||||
// ============================================================================
|
||||
AgentInfo BBLPrinterAgent::get_agent_info_static()
|
||||
{
|
||||
return AgentInfo{BBL_PRINTER_AGENT_ID, "Bambu Lab", "", "Bambu Lab printer agent"};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Print Job Operations
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_print();
|
||||
if (func && agent) {
|
||||
return func(agent, params, update_fn, cancel_fn, wait_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_local_print_with_record();
|
||||
if (func && agent) {
|
||||
return func(agent, params, update_fn, cancel_fn, wait_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_send_gcode_to_sdcard();
|
||||
if (func && agent) {
|
||||
return func(agent, params, update_fn, cancel_fn, wait_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_local_print();
|
||||
if (func && agent) {
|
||||
return func(agent, params, update_fn, cancel_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_start_sdcard_print();
|
||||
if (func && agent) {
|
||||
return func(agent, params, update_fn, cancel_fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
int BBLPrinterAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_ssdp_msg_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_on_printer_connected_fn(OnPrinterConnectedFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_printer_connected_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_on_subscribe_failure_fn(GetSubscribeFailureFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_subscribe_failure_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_on_message_fn(OnMessageFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_message_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_on_user_message_fn(OnMessageFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_user_message_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_on_local_connect_fn(OnLocalConnectedFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_local_connect_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_on_local_message_fn(OnMessageFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_on_local_message_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BBLPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
auto func = plugin.get_set_queue_on_main_fn();
|
||||
if (func && agent) {
|
||||
return func(agent, fn);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Filament Operations
|
||||
// ============================================================================
|
||||
|
||||
FilamentSyncMode BBLPrinterAgent::get_filament_sync_mode() const
|
||||
{
|
||||
// BBL uses MQTT subscription for real-time filament updates
|
||||
return FilamentSyncMode::subscription;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef __BBL_PRINTER_AGENT_HPP__
|
||||
#define __BBL_PRINTER_AGENT_HPP__
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* BBLPrinterAgent - BBL DLL wrapper implementation of IPrinterAgent.
|
||||
*
|
||||
* Delegates all printer operations to the proprietary BBL network DLL
|
||||
* through function pointers obtained from BBLNetworkPlugin singleton.
|
||||
*/
|
||||
class BBLPrinterAgent : public IPrinterAgent {
|
||||
public:
|
||||
BBLPrinterAgent();
|
||||
~BBLPrinterAgent() override;
|
||||
|
||||
// Cloud Agent Dependency (not used by BBL - tokens managed internally)
|
||||
void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) override;
|
||||
|
||||
// ========================================================================
|
||||
// IPrinterAgent Interface Implementation
|
||||
// ========================================================================
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||
int disconnect_printer() override;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
|
||||
// Certificates
|
||||
int check_cert() override;
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override;
|
||||
|
||||
// Discovery
|
||||
bool start_discovery(bool start, bool sending) override;
|
||||
|
||||
// Binding
|
||||
int ping_bind(std::string ping_code) override;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
std::string get_user_selected_machine() override;
|
||||
int set_user_selected_machine(std::string dev_id) override;
|
||||
|
||||
/**
|
||||
* Get agent information.
|
||||
*
|
||||
* @return AgentInfo struct containing agent identification and descriptive information
|
||||
*/
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
// Print Job Operations
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
|
||||
// Callbacks
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override;
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override;
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override;
|
||||
int set_on_message_fn(OnMessageFn fn) override;
|
||||
int set_on_user_message_fn(OnMessageFn fn) override;
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override;
|
||||
int set_on_local_message_fn(OnMessageFn fn) override;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
FilamentSyncMode get_filament_sync_mode() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __BBL_PRINTER_AGENT_HPP__
|
||||
@@ -1791,10 +1791,10 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
|
||||
print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
#else
|
||||
print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
|
||||
print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt;
|
||||
print_job->m_local_use_ssl = obj_->local_use_ssl;
|
||||
#endif
|
||||
|
||||
print_job->connection_type = obj_->connection_type();
|
||||
@@ -1896,10 +1896,10 @@ void CalibUtils::send_to_print(const std::vector<CalibInfo> &calib_infos, wxStri
|
||||
|
||||
#if !BBL_RELEASE_TO_PUBLIC
|
||||
print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false;
|
||||
print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false;
|
||||
#else
|
||||
print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp;
|
||||
print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt;
|
||||
print_job->m_local_use_ssl = obj_->local_use_ssl;
|
||||
#endif
|
||||
|
||||
print_job->connection_type = obj_->connection_type();
|
||||
|
||||
@@ -110,6 +110,9 @@ struct Http::priv
|
||||
::curl_httppost *form_end;
|
||||
::curl_mime* mime;
|
||||
::curl_slist *headerlist;
|
||||
// For debug printing
|
||||
std::string url;
|
||||
std::string method;
|
||||
// Used for reading the body
|
||||
std::string buffer;
|
||||
// Used for storing file streams added as multipart form parts
|
||||
@@ -170,6 +173,8 @@ Http::priv::priv(const std::string &url)
|
||||
, form_end(nullptr)
|
||||
, mime(nullptr)
|
||||
, headerlist(nullptr)
|
||||
, url(url)
|
||||
, method("GET")
|
||||
, error_buffer(CURL_ERROR_SIZE + 1, '\0')
|
||||
, limit(0)
|
||||
, cancel(false)
|
||||
@@ -757,6 +762,74 @@ void Http::cancel()
|
||||
if (p) { p->cancel = true; }
|
||||
}
|
||||
|
||||
void Http::print() const
|
||||
{
|
||||
if (!p) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Http::print() - no request data";
|
||||
return;
|
||||
}
|
||||
|
||||
std::ostringstream cmd;
|
||||
cmd << "curl";
|
||||
|
||||
// Method
|
||||
if (p->method != "GET") {
|
||||
cmd << " -X " << p->method;
|
||||
}
|
||||
|
||||
// URL
|
||||
cmd << " '" << p->url << "'";
|
||||
|
||||
// Headers (iterate through curl_slist)
|
||||
::curl_slist *header = p->headerlist;
|
||||
while (header) {
|
||||
// Skip empty "Expect:" header we add by default
|
||||
if (header->data && std::string(header->data) != "Expect:") {
|
||||
cmd << " \\\n -H '" << header->data << "'";
|
||||
}
|
||||
header = header->next;
|
||||
}
|
||||
|
||||
// Form fields (multipart) - iterate through curl_httppost
|
||||
::curl_httppost *formpost = p->form;
|
||||
while (formpost) {
|
||||
if (formpost->showfilename) {
|
||||
// File upload (showfilename is set when CURLFORM_FILENAME is used)
|
||||
cmd << " \\\n -F '" << formpost->name << "=@" << formpost->showfilename << "'";
|
||||
} else if (formpost->contents) {
|
||||
// Regular form field with contents
|
||||
cmd << " \\\n -F '" << formpost->name << "=" << formpost->contents << "'";
|
||||
} else {
|
||||
// Stream or other type without direct contents
|
||||
cmd << " \\\n -F '" << formpost->name << "=<data>'";
|
||||
}
|
||||
formpost = formpost->next;
|
||||
}
|
||||
|
||||
// Post body
|
||||
if (!p->postfields.empty()) {
|
||||
// Escape single quotes in the body for shell safety
|
||||
std::string escaped_body = p->postfields;
|
||||
size_t pos = 0;
|
||||
while ((pos = escaped_body.find('\'', pos)) != std::string::npos) {
|
||||
escaped_body.replace(pos, 1, "'\\''");
|
||||
pos += 4;
|
||||
}
|
||||
// Truncate if too long for display
|
||||
if (escaped_body.length() > 1000) {
|
||||
escaped_body = escaped_body.substr(0, 1000) + "...<truncated>";
|
||||
}
|
||||
cmd << " \\\n -d '" << escaped_body << "'";
|
||||
}
|
||||
|
||||
// Put file
|
||||
if (p->putFile) {
|
||||
cmd << " \\\n --upload-file <file-stream>";
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Http request:\n" << cmd.str();
|
||||
}
|
||||
|
||||
Http Http::get(std::string url)
|
||||
{
|
||||
return Http{std::move(url)};
|
||||
@@ -765,6 +838,7 @@ Http Http::get(std::string url)
|
||||
Http Http::post(std::string url)
|
||||
{
|
||||
Http http{std::move(url)};
|
||||
http.p->method = "POST";
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
|
||||
return http;
|
||||
}
|
||||
@@ -772,6 +846,7 @@ Http Http::post(std::string url)
|
||||
Http Http::put(std::string url)
|
||||
{
|
||||
Http http{std::move(url)};
|
||||
http.p->method = "PUT";
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_UPLOAD, 1L);
|
||||
return http;
|
||||
}
|
||||
@@ -779,6 +854,7 @@ Http Http::put(std::string url)
|
||||
Http Http::put2(std::string url)
|
||||
{
|
||||
Http http{ std::move(url) };
|
||||
http.p->method = "PUT";
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
return http;
|
||||
}
|
||||
@@ -786,6 +862,7 @@ Http Http::put2(std::string url)
|
||||
Http Http::patch(std::string url)
|
||||
{
|
||||
Http http{ std::move(url) };
|
||||
http.p->method = "PATCH";
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
return http;
|
||||
}
|
||||
@@ -793,6 +870,7 @@ Http Http::patch(std::string url)
|
||||
Http Http::del(std::string url)
|
||||
{
|
||||
Http http{ std::move(url) };
|
||||
http.p->method = "DELETE";
|
||||
curl_easy_setopt(http.p->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
return http;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,9 @@ public:
|
||||
// Cancels a request in progress
|
||||
void cancel();
|
||||
|
||||
// Print the request as a curl command for debugging
|
||||
void print() const;
|
||||
|
||||
// Tells whether current backend supports seting up a CA file using ca_file()
|
||||
static bool ca_file_supported();
|
||||
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
#ifndef __I_CLOUD_SERVICE_AGENT_HPP__
|
||||
#define __I_CLOUD_SERVICE_AGENT_HPP__
|
||||
|
||||
#include "bambu_networking.hpp"
|
||||
#include "../../libslic3r/ProjectTask.hpp"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* ICloudServiceAgent - Interface for authentication and cloud service operations.
|
||||
*
|
||||
* This interface encapsulates all cloud-related functionality including authentication:
|
||||
* - Lifecycle methods for agent initialization
|
||||
* - User session management (login/logout)
|
||||
* - Token access for dependent agents (IPrinterAgent)
|
||||
* - Login UI command builders for WebView integration
|
||||
* - Server connectivity and subscription management
|
||||
* - Settings synchronization (presets upload/download)
|
||||
* - Cloud user services (messages, tasks, firmware)
|
||||
* - Model mall and publishing
|
||||
* - Analytics and telemetry
|
||||
* - Ratings and reviews
|
||||
*
|
||||
* Implementations:
|
||||
* - OrcaCloudServiceAgent: Native implementation for Orca Cloud (includes OAuth PKCE)
|
||||
* - BBLCloudServiceAgent: Wrapper around Bambu Lab's proprietary DLL
|
||||
*
|
||||
* Token Sharing Pattern:
|
||||
* IPrinterAgent receives an ICloudServiceAgent instance via set_cloud_agent() to
|
||||
* access tokens for cloud-relay operations without coupling to a specific auth
|
||||
* implementation.
|
||||
*/
|
||||
class ICloudServiceAgent {
|
||||
public:
|
||||
virtual ~ICloudServiceAgent() = default;
|
||||
|
||||
// ========================================================================
|
||||
// Lifecycle Methods
|
||||
// ========================================================================
|
||||
/**
|
||||
* Initialize the logging backend for the agent.
|
||||
* Call after set_config_dir() so logs have a destination.
|
||||
*/
|
||||
virtual int init_log() = 0;
|
||||
|
||||
/**
|
||||
* Provide the writable configuration directory for storing auth state.
|
||||
* Must be called before start().
|
||||
*/
|
||||
virtual int set_config_dir(std::string config_dir) = 0;
|
||||
|
||||
/**
|
||||
* Register the client certificate file for TLS authentication.
|
||||
* May be unused by some implementations (e.g., OrcaCloudServiceAgent).
|
||||
*/
|
||||
virtual int set_cert_file(std::string folder, std::string filename) = 0;
|
||||
|
||||
/**
|
||||
* Set the country code for region-specific backend selection.
|
||||
*/
|
||||
virtual int set_country_code(std::string country_code) = 0;
|
||||
|
||||
/**
|
||||
* Start the agent, performing any expensive initialization.
|
||||
* Typically regenerates PKCE bundles and attempts silent sign-in.
|
||||
*/
|
||||
virtual int start() = 0;
|
||||
|
||||
// ========================================================================
|
||||
// User Session Management
|
||||
// ========================================================================
|
||||
/**
|
||||
* Authenticate the user with the provided JSON payload.
|
||||
*
|
||||
* Supported formats:
|
||||
* 1. Traditional: {"username": "...", "password": "..."}
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Check whether a valid authenticated session exists.
|
||||
*/
|
||||
virtual bool is_user_login() = 0;
|
||||
|
||||
/**
|
||||
* Terminate the current session.
|
||||
* @param request If true, also notify the backend to invalidate the session.
|
||||
*/
|
||||
virtual int user_logout(bool request = false) = 0;
|
||||
|
||||
/**
|
||||
* Return the backend-generated user ID for the current session.
|
||||
*/
|
||||
virtual std::string get_user_id() = 0;
|
||||
|
||||
/**
|
||||
* Return the display name for the current user.
|
||||
*/
|
||||
virtual std::string get_user_name() = 0;
|
||||
|
||||
/**
|
||||
* Return the avatar URL/path for the current user.
|
||||
*/
|
||||
virtual std::string get_user_avatar() = 0;
|
||||
|
||||
/**
|
||||
* Return the nickname for the current user.
|
||||
*/
|
||||
virtual std::string get_user_nickname() = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Login UI Support
|
||||
// ========================================================================
|
||||
/**
|
||||
* Build a JSON command for the WebView login flow.
|
||||
* Contains backend URL, API key, and PKCE parameters.
|
||||
*/
|
||||
virtual std::string build_login_cmd() = 0;
|
||||
|
||||
/**
|
||||
* Build a JSON command for WebView logout.
|
||||
*/
|
||||
virtual std::string build_logout_cmd() = 0;
|
||||
|
||||
/**
|
||||
* Return a JSON snapshot of the active session (user info, no tokens).
|
||||
* Used by WebView to display current user state.
|
||||
*/
|
||||
virtual std::string build_login_info() = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Token Access (for dependent agents)
|
||||
// ========================================================================
|
||||
/**
|
||||
* Return the current access token for API calls.
|
||||
* Cloud and printer agents use this for Authorization headers.
|
||||
*/
|
||||
virtual std::string get_access_token() const = 0;
|
||||
|
||||
/**
|
||||
* Return the current refresh token (if available).
|
||||
*/
|
||||
virtual std::string get_refresh_token() const = 0;
|
||||
|
||||
/**
|
||||
* Ensure the access token is fresh, refreshing if necessary.
|
||||
* Call before making API requests to avoid 401 errors.
|
||||
*
|
||||
* @param reason Descriptive string for logging (e.g., "connect_server")
|
||||
* @return true if the token is fresh or was successfully refreshed
|
||||
*/
|
||||
virtual bool ensure_token_fresh(const std::string& reason) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Server Connectivity
|
||||
// ========================================================================
|
||||
/**
|
||||
* Return the base hostname for cloud API calls (varies by region).
|
||||
* Helpful for diagnostics and when building browser URLs.
|
||||
*/
|
||||
virtual std::string get_cloud_service_host() = 0;
|
||||
|
||||
/**
|
||||
* Return the login URL for the cloud service.
|
||||
* @param language Optional language code (e.g., "en-US", "zh-CN") for localized login page.
|
||||
* If empty, returns the default (non-localized) login URL.
|
||||
* @return The full URL to the login page, or a local file:// URL for native implementations.
|
||||
*/
|
||||
virtual std::string get_cloud_login_url(const std::string& language = "") = 0;
|
||||
|
||||
/**
|
||||
* Perform a health check against the configured backend.
|
||||
* Updates is_server_connected() state and triggers OnServerConnectedFn.
|
||||
*/
|
||||
virtual int connect_server() = 0;
|
||||
|
||||
/**
|
||||
* Return whether the server is currently reachable.
|
||||
*/
|
||||
virtual bool is_server_connected() = 0;
|
||||
|
||||
/**
|
||||
* Force a server state recheck, clearing any cached state.
|
||||
*/
|
||||
virtual int refresh_connection() = 0;
|
||||
|
||||
/**
|
||||
* Subscribe to a logical module (e.g., "printer", "user").
|
||||
*/
|
||||
virtual int start_subscribe(std::string module) = 0;
|
||||
|
||||
/**
|
||||
* Stop listening to a formerly subscribed module.
|
||||
*/
|
||||
virtual int stop_subscribe(std::string module) = 0;
|
||||
|
||||
/**
|
||||
* Subscribe to push streams for specific device identifiers.
|
||||
*/
|
||||
virtual int add_subscribe(std::vector<std::string> dev_list) = 0;
|
||||
|
||||
/**
|
||||
* Remove device-level subscriptions.
|
||||
*/
|
||||
virtual int del_subscribe(std::vector<std::string> dev_list) = 0;
|
||||
|
||||
/**
|
||||
* Enable or disable multi-machine mode.
|
||||
*/
|
||||
virtual void enable_multi_machine(bool enable) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Settings Synchronization
|
||||
// ========================================================================
|
||||
/**
|
||||
* Fetch all presets owned by the logged-in user.
|
||||
* @param user_presets Map populated with [type][setting_id] = json
|
||||
*/
|
||||
virtual int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets) = 0;
|
||||
|
||||
/**
|
||||
* Request a new preset identifier from the server.
|
||||
*/
|
||||
virtual std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) = 0;
|
||||
|
||||
/**
|
||||
* Update or create a preset with a known setting_id.
|
||||
*/
|
||||
virtual int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) = 0;
|
||||
|
||||
/**
|
||||
* Trigger bulk download of user presets.
|
||||
*/
|
||||
virtual int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) = 0;
|
||||
|
||||
/**
|
||||
* Enhanced preset sync with per-item validation.
|
||||
*/
|
||||
virtual int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) = 0;
|
||||
|
||||
/**
|
||||
* Delete a remote preset.
|
||||
*/
|
||||
virtual int delete_setting(std::string setting_id) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Cloud User Services
|
||||
// ========================================================================
|
||||
/**
|
||||
* Retrieve inbox/notification messages.
|
||||
*/
|
||||
virtual int get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body) = 0;
|
||||
|
||||
/**
|
||||
* Check for pending task reports.
|
||||
*/
|
||||
virtual int check_user_task_report(int* task_id, bool* printable) = 0;
|
||||
|
||||
/**
|
||||
* Fetch aggregated print statistics.
|
||||
*/
|
||||
virtual int get_user_print_info(unsigned int* http_code, std::string* http_body) = 0;
|
||||
|
||||
/**
|
||||
* Query user's tasks/prints.
|
||||
*/
|
||||
virtual int get_user_tasks(TaskQueryParams params, std::string* http_body) = 0;
|
||||
|
||||
/**
|
||||
* Fetch firmware information for a printer.
|
||||
*/
|
||||
virtual int get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body) = 0;
|
||||
|
||||
/**
|
||||
* Get plate index for a cloud task.
|
||||
*/
|
||||
virtual int get_task_plate_index(std::string task_id, int* plate_index) = 0;
|
||||
|
||||
/**
|
||||
* Retrieve extended user profile info.
|
||||
*/
|
||||
virtual int get_user_info(int* identifier) = 0;
|
||||
|
||||
/**
|
||||
* Fetch subtask information.
|
||||
*/
|
||||
virtual int get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body) = 0;
|
||||
|
||||
/**
|
||||
* Retrieve slicing job info.
|
||||
*/
|
||||
virtual int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json) = 0;
|
||||
|
||||
/**
|
||||
* Query binding status for multiple devices.
|
||||
*/
|
||||
virtual int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body) = 0;
|
||||
|
||||
/**
|
||||
* Update printer name in cloud profile.
|
||||
*/
|
||||
virtual int modify_printer_name(std::string dev_id, std::string dev_name) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Model Mall & Publishing
|
||||
// ========================================================================
|
||||
/**
|
||||
* Request live camera streaming URL.
|
||||
*/
|
||||
virtual int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) = 0;
|
||||
|
||||
/**
|
||||
* Fetch staff-picked designs from model mall.
|
||||
*/
|
||||
virtual int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) = 0;
|
||||
|
||||
/**
|
||||
* Run multi-stage publishing workflow.
|
||||
*/
|
||||
virtual int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) = 0;
|
||||
|
||||
/**
|
||||
* Get model publish URL.
|
||||
*/
|
||||
virtual int get_model_publish_url(std::string* url) = 0;
|
||||
|
||||
/**
|
||||
* Fetch publishing subtask information.
|
||||
*/
|
||||
virtual int get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn) = 0;
|
||||
|
||||
/**
|
||||
* Get model mall home URL.
|
||||
*/
|
||||
virtual int get_model_mall_home_url(std::string* url) = 0;
|
||||
|
||||
/**
|
||||
* Build model detail page URL.
|
||||
*/
|
||||
virtual int get_model_mall_detail_url(std::string* url, std::string id) = 0;
|
||||
|
||||
/**
|
||||
* Retrieve user's model mall profile.
|
||||
*/
|
||||
virtual int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Analytics & Tracking
|
||||
// ========================================================================
|
||||
/**
|
||||
* Enable/disable telemetry.
|
||||
*/
|
||||
virtual int track_enable(bool enable) = 0;
|
||||
|
||||
/**
|
||||
* Delete telemetry files.
|
||||
*/
|
||||
virtual int track_remove_files() = 0;
|
||||
|
||||
/**
|
||||
* Report a custom analytics event.
|
||||
*/
|
||||
virtual int track_event(std::string evt_key, std::string content) = 0;
|
||||
|
||||
/**
|
||||
* Set telemetry headers.
|
||||
*/
|
||||
virtual int track_header(std::string header) = 0;
|
||||
|
||||
/**
|
||||
* Update a tracked user property.
|
||||
*/
|
||||
virtual int track_update_property(std::string name, std::string value, std::string type = "string") = 0;
|
||||
|
||||
/**
|
||||
* Read a tracked user property.
|
||||
*/
|
||||
virtual int track_get_property(std::string name, std::string& value, std::string type = "string") = 0;
|
||||
|
||||
/**
|
||||
* Check if tracking is enabled.
|
||||
*/
|
||||
virtual bool get_track_enable() = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Ratings & Reviews
|
||||
// ========================================================================
|
||||
/**
|
||||
* Submit a review for a marketplace design.
|
||||
*/
|
||||
virtual 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) = 0;
|
||||
|
||||
/**
|
||||
* Get OSS configuration for image uploads.
|
||||
*/
|
||||
virtual int get_oss_config(std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error) = 0;
|
||||
|
||||
/**
|
||||
* Upload rating images to OSS.
|
||||
*/
|
||||
virtual 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) = 0;
|
||||
|
||||
/**
|
||||
* Poll for rating result.
|
||||
*/
|
||||
virtual int get_model_mall_rating_result(int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// 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.
|
||||
*/
|
||||
virtual int get_mw_user_preference(std::function<void(std::string)> callback) = 0;
|
||||
|
||||
/**
|
||||
* Retrieve MakerWorld "For You" list.
|
||||
*/
|
||||
virtual int get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback) = 0;
|
||||
|
||||
/**
|
||||
* Return the version of the cloud service implementation.
|
||||
*/
|
||||
virtual std::string get_version() = 0;
|
||||
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Register HTTP error callback.
|
||||
*/
|
||||
virtual int set_on_http_error_fn(OnHttpErrorFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Provide country code getter callback.
|
||||
*/
|
||||
virtual int set_get_country_code_fn(GetCountryCodeFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Provide main thread queue callback.
|
||||
*/
|
||||
virtual int set_queue_on_main_fn(QueueOnMainFn fn) = 0;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __I_CLOUD_SERVICE_AGENT_HPP__
|
||||
@@ -0,0 +1,260 @@
|
||||
#ifndef __I_PRINTER_AGENT_HPP__
|
||||
#define __I_PRINTER_AGENT_HPP__
|
||||
|
||||
#include "bambu_networking.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ICloudServiceAgent;
|
||||
|
||||
/**
|
||||
* AgentInfo - Metadata structure for printer agent information.
|
||||
*
|
||||
* Contains identification and descriptive information about a printer agent
|
||||
* implementation, used for discovery and selection purposes.
|
||||
*/
|
||||
struct AgentInfo {
|
||||
std::string id; ///< Unique identifier for the agent, e.g. "orca", "bbl"
|
||||
std::string name; ///< Human-readable agent name, e.g. "Orca", "Bambu Lab"
|
||||
std::string version; ///< Agent version string, e.g. "1.0.0"
|
||||
std::string description; ///< Brief description of the agent's capabilities, e.g. "Orca printer agent"
|
||||
};
|
||||
|
||||
/**
|
||||
* FilamentSyncMode - Modes for filament data synchronization.
|
||||
*
|
||||
* Defines how filament information is obtained from the printer:
|
||||
* - Subscription: Real-time push updates (e.g., MQTT subscriptions)
|
||||
* - Pull: On-demand fetch via REST API (blocking call)
|
||||
* - None: Filament sync unavailable
|
||||
*/
|
||||
enum class FilamentSyncMode {
|
||||
none = 0, ///< Filament synchronization not supported
|
||||
subscription, ///< Real-time push updates via subscription (e.g., MQTT)
|
||||
pull ///< On-demand fetch via REST API (blocking call)
|
||||
};
|
||||
|
||||
/**
|
||||
* IPrinterAgent - Interface for printer operations.
|
||||
*
|
||||
* This interface encapsulates all printer-related functionality:
|
||||
* - Direct printer communication (LAN and cloud relay)
|
||||
* - Certificate management
|
||||
* - Device discovery (SSDP)
|
||||
* - Printer binding/unbinding
|
||||
* - Print job operations
|
||||
*
|
||||
* Implementations:
|
||||
* - OrcaPrinterAgent: Stub implementation (printer ops not yet supported)
|
||||
* - BBLPrinterAgent: Wrapper around Bambu Lab's proprietary DLL
|
||||
*
|
||||
* Token Access:
|
||||
* Printer agents receive an ICloudServiceAgent instance via set_cloud_agent() to
|
||||
* access tokens for cloud-relay operations.
|
||||
*/
|
||||
|
||||
class IPrinterAgent {
|
||||
public:
|
||||
virtual ~IPrinterAgent() = default;
|
||||
|
||||
// ========================================================================
|
||||
// Cloud Agent Dependency
|
||||
// ========================================================================
|
||||
/**
|
||||
* Set the cloud agent used for token access.
|
||||
* Must be called before any cloud-relay operations.
|
||||
*/
|
||||
virtual void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Communication
|
||||
// ========================================================================
|
||||
/**
|
||||
* Publish a JSON command to a printer through cloud relay.
|
||||
*/
|
||||
virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag) = 0;
|
||||
|
||||
/**
|
||||
* Establish a direct LAN connection to a printer.
|
||||
*/
|
||||
virtual int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) = 0;
|
||||
|
||||
/**
|
||||
* Tear down the active LAN printer connection.
|
||||
*/
|
||||
virtual int disconnect_printer() = 0;
|
||||
|
||||
/**
|
||||
* Send a JSON command to a LAN printer (bypassing cloud).
|
||||
*/
|
||||
virtual int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Certificates
|
||||
// ========================================================================
|
||||
/**
|
||||
* Validate current user certificates for the printer.
|
||||
*/
|
||||
virtual int check_cert() = 0;
|
||||
|
||||
/**
|
||||
* Install or refresh device certificate for LAN TLS.
|
||||
*/
|
||||
virtual void install_device_cert(std::string dev_id, bool lan_only) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Discovery
|
||||
// ========================================================================
|
||||
/**
|
||||
* Start or stop SSDP discovery.
|
||||
*/
|
||||
virtual bool start_discovery(bool start, bool sending) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Binding
|
||||
// ========================================================================
|
||||
/**
|
||||
* Ping the binding endpoint to check printer readiness.
|
||||
*/
|
||||
virtual int ping_bind(std::string ping_code) = 0;
|
||||
|
||||
/**
|
||||
* Perform binding detection/handshake on a LAN printer.
|
||||
*/
|
||||
virtual int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) = 0;
|
||||
|
||||
/**
|
||||
* Execute the multi-stage printer binding workflow.
|
||||
*/
|
||||
virtual int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) = 0;
|
||||
|
||||
/**
|
||||
* Remove the association between account and printer.
|
||||
*/
|
||||
virtual int unbind(std::string dev_id) = 0;
|
||||
|
||||
/**
|
||||
* Request a one-time bind ticket from the server.
|
||||
*/
|
||||
virtual int request_bind_ticket(std::string* ticket) = 0;
|
||||
|
||||
/**
|
||||
* Register callback for fatal HTTP errors.
|
||||
*/
|
||||
virtual int set_server_callback(OnServerErrFn fn) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Machine Selection
|
||||
// ========================================================================
|
||||
/**
|
||||
* Return the currently selected printer ID.
|
||||
*/
|
||||
virtual std::string get_user_selected_machine() = 0;
|
||||
|
||||
/**
|
||||
* Update the selected machine preference.
|
||||
*/
|
||||
virtual int set_user_selected_machine(std::string dev_id) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Print Job Operations
|
||||
// ========================================================================
|
||||
/**
|
||||
* Start a fully managed cloud print.
|
||||
*/
|
||||
virtual int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) = 0;
|
||||
|
||||
/**
|
||||
* Start a local print with cloud record.
|
||||
*/
|
||||
virtual int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) = 0;
|
||||
|
||||
/**
|
||||
* Upload gcode to printer's SD card without starting.
|
||||
*/
|
||||
virtual int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) = 0;
|
||||
|
||||
/**
|
||||
* Start a LAN-only print.
|
||||
*/
|
||||
virtual int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) = 0;
|
||||
|
||||
/**
|
||||
* Start a print from printer's SD card.
|
||||
*/
|
||||
virtual int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Callback Registration
|
||||
// ========================================================================
|
||||
/**
|
||||
* Register SSDP discovery callback.
|
||||
*/
|
||||
virtual int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register printer MQTT connection callback.
|
||||
*/
|
||||
virtual int set_on_printer_connected_fn(OnPrinterConnectedFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register subscription failure callback.
|
||||
*/
|
||||
virtual int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register cloud device message callback.
|
||||
*/
|
||||
virtual int set_on_message_fn(OnMessageFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register user-scoped message callback.
|
||||
*/
|
||||
virtual int set_on_user_message_fn(OnMessageFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register LAN connection status callback.
|
||||
*/
|
||||
virtual int set_on_local_connect_fn(OnLocalConnectedFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Register LAN message callback.
|
||||
*/
|
||||
virtual int set_on_local_message_fn(OnMessageFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Provide main thread queue callback.
|
||||
*/
|
||||
virtual int set_queue_on_main_fn(QueueOnMainFn fn) = 0;
|
||||
|
||||
/**
|
||||
* Get agent information.
|
||||
*/
|
||||
virtual AgentInfo get_agent_info() = 0;
|
||||
|
||||
// ========================================================================
|
||||
// Filament Operations
|
||||
// ========================================================================
|
||||
/**
|
||||
* Get the filament synchronization mode for this agent.
|
||||
*
|
||||
* @return FilamentSyncMode indicating how filament data is obtained:
|
||||
* - subscription: Real-time push updates via MQTT (no fetch needed)
|
||||
* - pull: On-demand fetch via REST API (call fetch_filament_info())
|
||||
* - none: Filament synchronization not supported
|
||||
*/
|
||||
virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; }
|
||||
|
||||
/**
|
||||
* Refresh filament info from the printer synchronously.
|
||||
* Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull.
|
||||
* Populates the MachineObject's DevFilaSystem with fetched filament data.
|
||||
*/
|
||||
virtual bool fetch_filament_info(std::string dev_id) { return false; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __I_PRINTER_AGENT_HPP__
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
#ifndef __MOONRAKER_PRINTER_AGENT_HPP__
|
||||
#define __MOONRAKER_PRINTER_AGENT_HPP__
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class MoonrakerPrinterAgent : public IPrinterAgent
|
||||
{
|
||||
public:
|
||||
explicit MoonrakerPrinterAgent(std::string log_dir);
|
||||
~MoonrakerPrinterAgent() override;
|
||||
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
// Cloud Agent Dependency
|
||||
void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) override;
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||
int disconnect_printer() override;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
|
||||
// Certificates
|
||||
int check_cert() override;
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override;
|
||||
|
||||
// Discovery
|
||||
bool start_discovery(bool start, bool sending) override;
|
||||
|
||||
// Binding
|
||||
int ping_bind(std::string ping_code) override;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
std::string get_user_selected_machine() override;
|
||||
int set_user_selected_machine(std::string dev_id) override;
|
||||
|
||||
// Print Job Operations
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
|
||||
// Callbacks
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override;
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override;
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override;
|
||||
int set_on_message_fn(OnMessageFn fn) override;
|
||||
int set_on_user_message_fn(OnMessageFn fn) override;
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override;
|
||||
int set_on_local_message_fn(OnMessageFn fn) override;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
|
||||
// Pull-mode agent (on-demand filament sync)
|
||||
FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; }
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
|
||||
protected:
|
||||
struct MoonrakerDeviceInfo
|
||||
{
|
||||
std::string dev_id;
|
||||
std::string dev_ip;
|
||||
std::string api_key;
|
||||
std::string base_url;
|
||||
std::string model_id;
|
||||
std::string model_name;
|
||||
std::string dev_name;
|
||||
std::string version;
|
||||
std::string klippy_state;
|
||||
bool use_ssl = false;
|
||||
} device_info;
|
||||
|
||||
// Tray data for AMS payload building
|
||||
struct AmsTrayData {
|
||||
int slot_index = 0; // 0-based slot index
|
||||
bool has_filament = false;
|
||||
std::string tray_type; // Material type (e.g., "PLA", "ASA")
|
||||
std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA)
|
||||
std::string tray_info_idx; // Setting ID (optional)
|
||||
int bed_temp = 0; // Optional
|
||||
int nozzle_temp = 0; // Optional
|
||||
};
|
||||
|
||||
// Build ams JSON and call parser
|
||||
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
|
||||
|
||||
// Methods that derived classes may need to override or access
|
||||
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
|
||||
|
||||
// State access for derived classes
|
||||
mutable std::recursive_mutex state_mutex;
|
||||
|
||||
// Helpers
|
||||
bool is_numeric(const std::string& value);
|
||||
std::string normalize_base_url(std::string host, const std::string& port);
|
||||
std::string sanitize_filename(const std::string& filename);
|
||||
std::string join_url(const std::string& base_url, const std::string& path) const;
|
||||
|
||||
// Trim whitespace and convert to uppercase
|
||||
static std::string trim_and_upper(const std::string& input);
|
||||
|
||||
// Map filament type to OrcaFilamentLibrary preset ID for AMS sync compatibility
|
||||
static std::string map_filament_type_to_generic_id(const std::string& filament_type);
|
||||
|
||||
private:
|
||||
int handle_request(const std::string& dev_id, const std::string& json_str);
|
||||
int send_version_info(const std::string& dev_id);
|
||||
int send_access_code(const std::string& dev_id);
|
||||
|
||||
bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set<std::string>& objects, std::string& error) const;
|
||||
bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const;
|
||||
bool send_gcode(const std::string& dev_id, const std::string& gcode) const;
|
||||
|
||||
void announce_printhost_device();
|
||||
void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg);
|
||||
void dispatch_printer_connected(const std::string& dev_id);
|
||||
void dispatch_message(const std::string& dev_id, const std::string& payload);
|
||||
void start_status_stream(const std::string& dev_id, const std::string& base_url, const std::string& api_key);
|
||||
void stop_status_stream();
|
||||
void run_status_stream(std::string dev_id, std::string base_url, std::string api_key);
|
||||
void handle_ws_message(const std::string& dev_id, const std::string& payload);
|
||||
void update_status_cache(const nlohmann::json& updates);
|
||||
nlohmann::json build_print_payload_locked() const;
|
||||
|
||||
// Print control helpers
|
||||
int pause_print(const std::string& dev_id);
|
||||
int resume_print(const std::string& dev_id);
|
||||
int cancel_print(const std::string& dev_id);
|
||||
|
||||
// File upload
|
||||
bool upload_gcode(const std::string& local_path, const std::string& filename,
|
||||
const std::string& base_url, const std::string& api_key,
|
||||
OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
|
||||
// JSON-RPC helper
|
||||
bool send_jsonrpc_command(const std::string& base_url, const std::string& api_key,
|
||||
const nlohmann::json& request, std::string& response) const;
|
||||
|
||||
// Connection thread management
|
||||
void perform_connection_async(const std::string& dev_id,
|
||||
const std::string& base_url,
|
||||
const std::string& api_key,
|
||||
uint64_t generation);
|
||||
|
||||
std::string ssdp_announced_host;
|
||||
std::string ssdp_announced_id;
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
std::string selected_machine;
|
||||
|
||||
OnMsgArrivedFn on_ssdp_msg_fn;
|
||||
OnPrinterConnectedFn on_printer_connected_fn;
|
||||
GetSubscribeFailureFn on_subscribe_failure_fn;
|
||||
OnMessageFn on_message_fn;
|
||||
OnMessageFn on_user_message_fn;
|
||||
OnLocalConnectedFn on_local_connect_fn;
|
||||
OnMessageFn on_local_message_fn;
|
||||
QueueOnMainFn queue_on_main_fn;
|
||||
OnServerErrFn on_server_err_fn;
|
||||
|
||||
mutable std::recursive_mutex payload_mutex;
|
||||
nlohmann::json status_cache;
|
||||
|
||||
std::atomic<int> next_jsonrpc_id{1};
|
||||
std::set<std::string> available_objects; // Track for feature detection
|
||||
|
||||
std::atomic<bool> ws_stop{false};
|
||||
std::atomic<bool> ws_reconnect_requested{false}; // Flag to trigger reconnection
|
||||
std::atomic<uint64_t> ws_last_emit_ms{0};
|
||||
std::thread ws_thread;
|
||||
|
||||
// Throttling configuration for WebSocket updates
|
||||
// Critical changes (state transitions) dispatch immediately; telemetry is throttled
|
||||
static constexpr uint64_t STATUS_UPDATE_INTERVAL_MS = 1000; // 1 update/sec for telemetry
|
||||
std::atomic<uint64_t> ws_last_dispatch_ms{0};
|
||||
std::string last_print_state; // Track state for immediate dispatch on change
|
||||
|
||||
// Connection thread management
|
||||
std::atomic<uint64_t> connect_generation{0};
|
||||
std::thread connect_thread;
|
||||
std::recursive_mutex connect_mutex;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
+408
-1473
File diff suppressed because it is too large
Load Diff
@@ -3,118 +3,22 @@
|
||||
|
||||
#include "bambu_networking.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include <memory>
|
||||
|
||||
using namespace BBL;
|
||||
|
||||
namespace Slic3r {
|
||||
typedef bool (*func_check_debug_consistent)(bool is_debug);
|
||||
typedef std::string (*func_get_version)(void);
|
||||
typedef void* (*func_create_agent)(std::string log_dir);
|
||||
typedef int (*func_destroy_agent)(void *agent);
|
||||
typedef int (*func_init_log)(void *agent);
|
||||
typedef int (*func_set_config_dir)(void *agent, std::string config_dir);
|
||||
typedef int (*func_set_cert_file)(void *agent, std::string folder, std::string filename);
|
||||
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);
|
||||
typedef int (*func_set_get_country_code_fn)(void *agent, GetCountryCodeFn fn);
|
||||
typedef int (*func_set_on_subscribe_failure_fn)(void *agent, GetSubscribeFailureFn fn);
|
||||
typedef int (*func_set_on_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_on_user_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_on_local_connect_fn)(void *agent, OnLocalConnectedFn fn);
|
||||
typedef int (*func_set_on_local_message_fn)(void *agent, OnMessageFn fn);
|
||||
typedef int (*func_set_queue_on_main_fn)(void *agent, QueueOnMainFn fn);
|
||||
typedef int (*func_connect_server)(void *agent);
|
||||
typedef bool (*func_is_server_connected)(void *agent);
|
||||
typedef int (*func_refresh_connection)(void *agent);
|
||||
typedef int (*func_start_subscribe)(void *agent, std::string module);
|
||||
typedef int (*func_stop_subscribe)(void *agent, std::string module);
|
||||
typedef int (*func_add_subscribe)(void *agent, std::vector<std::string> dev_list);
|
||||
typedef int (*func_del_subscribe)(void *agent, std::vector<std::string> dev_list);
|
||||
typedef void (*func_enable_multi_machine)(void *agent, bool enable);
|
||||
typedef int (*func_send_message)(void *agent, std::string dev_id, std::string json_str, int qos, int flag);
|
||||
typedef int (*func_connect_printer)(void *agent, std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
typedef int (*func_disconnect_printer)(void *agent);
|
||||
typedef int (*func_send_message_to_printer)(void *agent, std::string dev_id, std::string json_str, int qos, int flag);
|
||||
typedef int (*func_check_cert)(void* agent);
|
||||
typedef void (*func_install_device_cert)(void* agent, std::string dev_id, bool lan_only);
|
||||
typedef bool (*func_start_discovery)(void *agent, bool start, bool sending);
|
||||
typedef int (*func_change_user)(void *agent, std::string user_info);
|
||||
typedef bool (*func_is_user_login)(void *agent);
|
||||
typedef int (*func_user_logout)(void *agent, bool request);
|
||||
typedef std::string (*func_get_user_id)(void *agent);
|
||||
typedef std::string (*func_get_user_name)(void *agent);
|
||||
typedef std::string (*func_get_user_avatar)(void *agent);
|
||||
typedef std::string (*func_get_user_nickanme)(void *agent);
|
||||
typedef std::string (*func_build_login_cmd)(void *agent);
|
||||
typedef std::string (*func_build_logout_cmd)(void *agent);
|
||||
typedef std::string (*func_build_login_info)(void *agent);
|
||||
typedef int (*func_ping_bind)(void *agent, std::string ping_code);
|
||||
typedef int (*func_bind_detect)(void *agent, std::string dev_ip, std::string sec_link, detectResult& detect);
|
||||
typedef int (*func_set_server_callback)(void *agent, OnServerErrFn fn);
|
||||
typedef int (*func_bind)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
|
||||
typedef int (*func_unbind)(void *agent, std::string dev_id);
|
||||
typedef std::string (*func_get_bambulab_host)(void *agent);
|
||||
typedef std::string (*func_get_user_selected_machine)(void *agent);
|
||||
typedef int (*func_set_user_selected_machine)(void *agent, std::string dev_id);
|
||||
typedef int (*func_start_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print_with_record)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_send_gcode_to_sdcard)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
typedef int (*func_start_local_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_start_sdcard_print)(void *agent, PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
typedef int (*func_get_user_presets)(void *agent, std::map<std::string, std::map<std::string, std::string>>* user_presets);
|
||||
typedef std::string (*func_request_setting_id)(void *agent, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
typedef int (*func_put_setting)(void *agent, std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code);
|
||||
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);
|
||||
typedef int (*func_get_user_print_info)(void *agent, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_get_user_tasks)(void *agent, TaskQueryParams params, std::string* http_body);
|
||||
typedef int (*func_get_printer_firmware)(void *agent, std::string dev_id, unsigned* http_code, std::string* http_body);
|
||||
typedef int (*func_get_task_plate_index)(void *agent, std::string task_id, int* plate_index);
|
||||
typedef int (*func_get_user_info)(void *agent, int* identifier);
|
||||
typedef int (*func_request_bind_ticket)(void *agent, std::string* ticket);
|
||||
typedef int (*func_get_subtask_info)(void *agent, std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string *http_body);
|
||||
typedef int (*func_get_slice_info)(void *agent, std::string project_id, std::string profile_id, int plate_index, std::string* slice_json);
|
||||
typedef int (*func_query_bind_status)(void *agent, std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body);
|
||||
typedef int (*func_modify_printer_name)(void *agent, std::string dev_id, std::string dev_name);
|
||||
typedef int (*func_get_camera_url)(void *agent, std::string dev_id, std::function<void(std::string)> callback);
|
||||
typedef int (*func_get_design_staffpick)(void *agent, int offset, int limit, std::function<void(std::string)> callback);
|
||||
typedef int (*func_start_pubilsh)(void *agent, PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out);
|
||||
typedef int (*func_get_model_publish_url)(void *agent, std::string* url);
|
||||
typedef int (*func_get_subtask)(void *agent, BBLModelTask* task, OnGetSubTaskFn getsub_fn);
|
||||
typedef int (*func_get_model_mall_home_url)(void *agent, std::string* url);
|
||||
typedef int (*func_get_model_mall_detail_url)(void *agent, std::string* url, std::string id);
|
||||
typedef int (*func_get_my_profile)(void *agent, std::string token, unsigned int *http_code, std::string *http_body);
|
||||
typedef int (*func_track_enable)(void *agent, bool enable);
|
||||
typedef int (*func_track_remove_files)(void *agent);
|
||||
typedef int (*func_track_event)(void *agent, std::string evt_key, std::string content);
|
||||
typedef int (*func_track_header)(void *agent, std::string header);
|
||||
typedef int (*func_track_update_property)(void *agent, std::string name, std::string value, std::string type);
|
||||
typedef int (*func_track_get_property)(void *agent, std::string name, std::string& value, std::string type);
|
||||
typedef int (*func_put_model_mall_rating_url)(
|
||||
void *agent, int rating_id, int score, std::string content, std::vector<std::string> images, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_oss_config)(void *agent, std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_put_rating_picture_oss)(
|
||||
void *agent, std::string &config, std::string &pic_oss_path, std::string model_id, int profile_id, unsigned int &http_code, std::string &http_error);
|
||||
typedef int (*func_get_model_mall_rating_result)(void *agent, int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error);
|
||||
|
||||
typedef int (*func_get_mw_user_preference)(void *agent, std::function<void(std::string)> callback);
|
||||
typedef int (*func_get_mw_user_4ulist)(void *agent, int seed, int limit, std::function<void(std::string)> callback);
|
||||
// Forward declaration
|
||||
class BBLNetworkPlugin;
|
||||
|
||||
//the NetworkAgent class
|
||||
class NetworkAgent
|
||||
{
|
||||
|
||||
public:
|
||||
// Static utility methods - delegate to BBLNetworkPlugin
|
||||
static std::string get_libpath_in_current_directory(std::string library_name);
|
||||
static std::string get_versioned_library_path(const std::string& version);
|
||||
static bool versioned_library_exists(const std::string& version);
|
||||
@@ -136,9 +40,24 @@ public:
|
||||
static NetworkLibraryLoadError get_load_error();
|
||||
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);
|
||||
|
||||
~NetworkAgent();
|
||||
|
||||
// Sub-agent accessors
|
||||
std::shared_ptr<ICloudServiceAgent> get_cloud_agent() const { return m_cloud_agent; }
|
||||
std::shared_ptr<IPrinterAgent> get_printer_agent() const { return m_printer_agent; }
|
||||
|
||||
// Set the printer agent (for dynamic agent switching)
|
||||
void set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent);
|
||||
|
||||
// Instance methods - delegate to sub-agents or BBLNetworkPlugin
|
||||
int init_log();
|
||||
int set_config_dir(std::string config_dir);
|
||||
int set_cert_file(std::string folder, std::string filename);
|
||||
@@ -177,7 +96,7 @@ public:
|
||||
std::string get_user_id();
|
||||
std::string get_user_name();
|
||||
std::string get_user_avatar();
|
||||
std::string get_user_nickanme();
|
||||
std::string get_user_nickname();
|
||||
std::string build_login_cmd();
|
||||
std::string build_logout_cmd();
|
||||
std::string build_login_info();
|
||||
@@ -186,7 +105,8 @@ public:
|
||||
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_bambulab_host();
|
||||
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_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
@@ -194,6 +114,8 @@ public:
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
|
||||
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);
|
||||
@@ -236,117 +158,36 @@ public:
|
||||
|
||||
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);
|
||||
void *get_network_agent() { return network_agent; }
|
||||
|
||||
// Get underlying agent handle from BBLNetworkPlugin
|
||||
void* get_network_agent();
|
||||
|
||||
private:
|
||||
struct PrinterCallbacks {
|
||||
OnMsgArrivedFn on_ssdp_msg_fn = nullptr;
|
||||
OnPrinterConnectedFn on_printer_connected_fn = nullptr;
|
||||
GetSubscribeFailureFn on_subscribe_failure_fn = nullptr;
|
||||
OnMessageFn on_message_fn = nullptr;
|
||||
OnMessageFn on_user_message_fn = nullptr;
|
||||
OnLocalConnectedFn on_local_connect_fn = nullptr;
|
||||
OnMessageFn on_local_message_fn = nullptr;
|
||||
QueueOnMainFn queue_on_main_fn = nullptr;
|
||||
OnServerErrFn on_server_err_fn = nullptr;
|
||||
};
|
||||
|
||||
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;
|
||||
void* network_agent { nullptr };
|
||||
|
||||
static NetworkLibraryLoadError s_load_error;
|
||||
|
||||
static func_check_debug_consistent check_debug_consistent_ptr;
|
||||
static func_get_version get_version_ptr;
|
||||
static func_create_agent create_agent_ptr;
|
||||
static func_destroy_agent destroy_agent_ptr;
|
||||
static func_init_log init_log_ptr;
|
||||
static func_set_config_dir set_config_dir_ptr;
|
||||
static func_set_cert_file set_cert_file_ptr;
|
||||
static func_set_country_code set_country_code_ptr;
|
||||
static func_start start_ptr;
|
||||
static func_set_on_ssdp_msg_fn set_on_ssdp_msg_fn_ptr;
|
||||
static func_set_on_user_login_fn set_on_user_login_fn_ptr;
|
||||
static func_set_on_printer_connected_fn set_on_printer_connected_fn_ptr;
|
||||
static func_set_on_server_connected_fn set_on_server_connected_fn_ptr;
|
||||
static func_set_on_http_error_fn set_on_http_error_fn_ptr;
|
||||
static func_set_get_country_code_fn set_get_country_code_fn_ptr;
|
||||
static func_set_on_subscribe_failure_fn set_on_subscribe_failure_fn_ptr;
|
||||
static func_set_on_message_fn set_on_message_fn_ptr;
|
||||
static func_set_on_user_message_fn set_on_user_message_fn_ptr;
|
||||
static func_set_on_local_connect_fn set_on_local_connect_fn_ptr;
|
||||
static func_set_on_local_message_fn set_on_local_message_fn_ptr;
|
||||
static func_set_queue_on_main_fn set_queue_on_main_fn_ptr;
|
||||
static func_connect_server connect_server_ptr;
|
||||
static func_is_server_connected is_server_connected_ptr;
|
||||
static func_refresh_connection refresh_connection_ptr;
|
||||
static func_start_subscribe start_subscribe_ptr;
|
||||
static func_stop_subscribe stop_subscribe_ptr;
|
||||
static func_add_subscribe add_subscribe_ptr;
|
||||
static func_del_subscribe del_subscribe_ptr;
|
||||
static func_enable_multi_machine enable_multi_machine_ptr;
|
||||
static func_send_message send_message_ptr;
|
||||
static func_connect_printer connect_printer_ptr;
|
||||
static func_disconnect_printer disconnect_printer_ptr;
|
||||
static func_send_message_to_printer send_message_to_printer_ptr;
|
||||
static func_check_cert check_cert_ptr;
|
||||
static func_install_device_cert install_device_cert_ptr;
|
||||
static func_start_discovery start_discovery_ptr;
|
||||
static func_change_user change_user_ptr;
|
||||
static func_is_user_login is_user_login_ptr;
|
||||
static func_user_logout user_logout_ptr;
|
||||
static func_get_user_id get_user_id_ptr;
|
||||
static func_get_user_name get_user_name_ptr;
|
||||
static func_get_user_avatar get_user_avatar_ptr;
|
||||
static func_get_user_nickanme get_user_nickanme_ptr;
|
||||
static func_build_login_cmd build_login_cmd_ptr;
|
||||
static func_build_logout_cmd build_logout_cmd_ptr;
|
||||
static func_build_login_info build_login_info_ptr;
|
||||
static func_ping_bind ping_bind_ptr;
|
||||
static func_bind_detect bind_detect_ptr;
|
||||
static func_set_server_callback set_server_callback_ptr;
|
||||
static func_bind bind_ptr;
|
||||
static func_unbind unbind_ptr;
|
||||
static func_get_bambulab_host get_bambulab_host_ptr;
|
||||
static func_get_user_selected_machine get_user_selected_machine_ptr;
|
||||
static func_set_user_selected_machine set_user_selected_machine_ptr;
|
||||
static func_start_print start_print_ptr;
|
||||
static func_start_local_print_with_record start_local_print_with_record_ptr;
|
||||
static func_start_send_gcode_to_sdcard start_send_gcode_to_sdcard_ptr;
|
||||
static func_start_local_print start_local_print_ptr;
|
||||
static func_start_sdcard_print start_sdcard_print_ptr;
|
||||
static func_get_user_presets get_user_presets_ptr;
|
||||
static func_request_setting_id request_setting_id_ptr;
|
||||
static func_put_setting put_setting_ptr;
|
||||
static func_get_setting_list get_setting_list_ptr;
|
||||
static func_get_setting_list2 get_setting_list2_ptr;
|
||||
static func_delete_setting delete_setting_ptr;
|
||||
static func_get_studio_info_url get_studio_info_url_ptr;
|
||||
static func_set_extra_http_header set_extra_http_header_ptr;
|
||||
static func_get_my_message get_my_message_ptr;
|
||||
static func_check_user_task_report check_user_task_report_ptr;
|
||||
static func_get_user_print_info get_user_print_info_ptr;
|
||||
static func_get_user_tasks get_user_tasks_ptr;
|
||||
static func_get_printer_firmware get_printer_firmware_ptr;
|
||||
static func_get_task_plate_index get_task_plate_index_ptr;
|
||||
static func_get_user_info get_user_info_ptr;
|
||||
static func_request_bind_ticket request_bind_ticket_ptr;
|
||||
static func_get_subtask_info get_subtask_info_ptr;
|
||||
static func_get_slice_info get_slice_info_ptr;
|
||||
static func_query_bind_status query_bind_status_ptr;
|
||||
static func_modify_printer_name modify_printer_name_ptr;
|
||||
static func_get_camera_url get_camera_url_ptr;
|
||||
static func_get_design_staffpick get_design_staffpick_ptr;
|
||||
static func_start_pubilsh start_publish_ptr;
|
||||
static func_get_model_publish_url get_model_publish_url_ptr;
|
||||
static func_get_subtask get_subtask_ptr;
|
||||
static func_get_model_mall_home_url get_model_mall_home_url_ptr;
|
||||
static func_get_model_mall_detail_url get_model_mall_detail_url_ptr;
|
||||
static func_get_my_profile get_my_profile_ptr;
|
||||
static func_track_enable track_enable_ptr;
|
||||
static func_track_remove_files track_remove_files_ptr;
|
||||
static func_track_event track_event_ptr;
|
||||
static func_track_header track_header_ptr;
|
||||
static func_track_update_property track_update_property_ptr;
|
||||
static func_track_get_property track_get_property_ptr;
|
||||
static func_put_model_mall_rating_url put_model_mall_rating_url_ptr;
|
||||
static func_get_oss_config get_oss_config_ptr;
|
||||
static func_put_rating_picture_oss put_rating_picture_oss_ptr;
|
||||
static func_get_model_mall_rating_result get_model_mall_rating_result_ptr;
|
||||
|
||||
static func_get_mw_user_preference get_mw_user_preference_ptr;
|
||||
static func_get_mw_user_4ulist get_mw_user_4ulist_ptr;
|
||||
// Sub-agent composition (for Orca/BBL mixed mode)
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
std::shared_ptr<IPrinterAgent> m_printer_agent;
|
||||
std::string m_printer_agent_id;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include "BBLPrinterAgent.hpp"
|
||||
#include "OrcaPrinterAgent.hpp"
|
||||
#include "QidiPrinterAgent.hpp"
|
||||
#include "SnapmakerPrinterAgent.hpp"
|
||||
#include "MoonrakerPrinterAgent.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
static std::mutex s_registry_mutex;
|
||||
|
||||
std::map<std::string, PrinterAgentInfo>& get_printer_agents()
|
||||
{
|
||||
static std::map<std::string, PrinterAgentInfo> agents;
|
||||
return agents;
|
||||
}
|
||||
|
||||
std::map<std::string, std::shared_ptr<IPrinterAgent>>& get_printer_agent_cache()
|
||||
{
|
||||
static std::map<std::string, std::shared_ptr<IPrinterAgent>> cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Helper to register a printer agent type with the standard factory pattern.
|
||||
// AgentTypes that take a log_dir constructor arg use the default; BBLPrinterAgent
|
||||
// (no log_dir) is registered separately.
|
||||
template<typename T>
|
||||
void register_agent()
|
||||
{
|
||||
auto info = T::get_agent_info_static();
|
||||
NetworkAgentFactory::register_printer_agent(
|
||||
info.id, info.name,
|
||||
[](std::shared_ptr<ICloudServiceAgent> cloud_agent,
|
||||
const std::string& log_dir) -> std::shared_ptr<IPrinterAgent> {
|
||||
auto agent = std::make_shared<T>(log_dir);
|
||||
if (cloud_agent)
|
||||
agent->set_cloud_agent(cloud_agent);
|
||||
return agent;
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool NetworkAgentFactory::register_printer_agent(const std::string& id, const std::string& display_name, PrinterAgentFactory factory)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
auto& agents = get_printer_agents();
|
||||
return agents.emplace(id, PrinterAgentInfo(id, display_name, std::move(factory))).second;
|
||||
}
|
||||
|
||||
bool NetworkAgentFactory::is_printer_agent_registered(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
auto& agents = get_printer_agents();
|
||||
return agents.find(id) != agents.end();
|
||||
}
|
||||
|
||||
const PrinterAgentInfo* NetworkAgentFactory::get_printer_agent_info(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
auto& agents = get_printer_agents();
|
||||
auto it = agents.find(id);
|
||||
return (it != agents.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
std::vector<PrinterAgentInfo> NetworkAgentFactory::get_registered_printer_agents()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
auto& agents = get_printer_agents();
|
||||
std::vector<PrinterAgentInfo> result;
|
||||
result.reserve(agents.size());
|
||||
|
||||
for (const auto& pair : agents) {
|
||||
result.push_back(pair.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<IPrinterAgent> NetworkAgentFactory::create_printer_agent_by_id(const std::string& id,
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent,
|
||||
const std::string& log_dir)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
|
||||
// Check cache first
|
||||
auto& cache = get_printer_agent_cache();
|
||||
auto cache_it = cache.find(id);
|
||||
if (cache_it != cache.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Reusing cached printer agent: " << id;
|
||||
if (cloud_agent)
|
||||
cache_it->second->set_cloud_agent(cloud_agent);
|
||||
return cache_it->second;
|
||||
}
|
||||
|
||||
// Not cached — create via factory
|
||||
auto& agents = get_printer_agents();
|
||||
auto it = agents.find(id);
|
||||
|
||||
if (it == agents.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Unknown printer agent ID: " << id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto agent = it->second.factory(cloud_agent, log_dir);
|
||||
if (agent) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Created and cached printer agent: " << id;
|
||||
cache[id] = agent;
|
||||
}
|
||||
return agent;
|
||||
}
|
||||
|
||||
void NetworkAgentFactory::clear_printer_agent_cache()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_registry_mutex);
|
||||
auto& cache = get_printer_agent_cache();
|
||||
for (auto& pair : cache) {
|
||||
if (pair.second)
|
||||
pair.second->disconnect_printer();
|
||||
}
|
||||
cache.clear();
|
||||
BOOST_LOG_TRIVIAL(info) << "Printer agent cache cleared";
|
||||
}
|
||||
|
||||
void NetworkAgentFactory::register_all_agents()
|
||||
{
|
||||
register_agent<OrcaPrinterAgent>();
|
||||
register_agent<QidiPrinterAgent>();
|
||||
register_agent<SnapmakerPrinterAgent>();
|
||||
register_agent<MoonrakerPrinterAgent>();
|
||||
|
||||
// BBLPrinterAgent takes no constructor args, so register manually
|
||||
{
|
||||
auto info = BBLPrinterAgent::get_agent_info_static();
|
||||
register_printer_agent(info.id, info.name,
|
||||
[](std::shared_ptr<ICloudServiceAgent> cloud_agent,
|
||||
const std::string& /*log_dir*/) -> std::shared_ptr<IPrinterAgent> {
|
||||
auto agent = std::make_shared<BBLPrinterAgent>();
|
||||
if (cloud_agent)
|
||||
agent->set_cloud_agent(cloud_agent);
|
||||
return agent;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<NetworkAgent> create_agent_from_config(const std::string& log_dir, AppConfig* app_config)
|
||||
{
|
||||
if (!app_config)
|
||||
return std::make_unique<NetworkAgent>(nullptr, nullptr);
|
||||
|
||||
// Determine cloud provider from config
|
||||
bool use_orca_cloud = app_config->get_bool("use_orca_cloud");
|
||||
|
||||
// Create cloud agent
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent;
|
||||
if (use_orca_cloud || app_config->get_bool("installed_networking")) {
|
||||
CloudAgentProvider provider = use_orca_cloud ? CloudAgentProvider::Orca : CloudAgentProvider::BBL;
|
||||
cloud_agent = NetworkAgentFactory::create_cloud_agent(provider, log_dir);
|
||||
if (!cloud_agent) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to create cloud agent";
|
||||
}
|
||||
}
|
||||
|
||||
// Create NetworkAgent with cloud agent only (printer agent added later when printer is selected)
|
||||
auto agent = std::make_unique<NetworkAgent>(std::move(cloud_agent), nullptr);
|
||||
|
||||
if (agent && use_orca_cloud) {
|
||||
auto* orca_cloud = dynamic_cast<OrcaCloudServiceAgent*>(agent->get_cloud_agent().get());
|
||||
if (orca_cloud) {
|
||||
orca_cloud->configure_urls(app_config);
|
||||
}
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,187 @@
|
||||
#ifndef __NETWORK_AGENT_FACTORY_HPP__
|
||||
#define __NETWORK_AGENT_FACTORY_HPP__
|
||||
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "NetworkAgent.hpp"
|
||||
#include "OrcaCloudServiceAgent.hpp"
|
||||
#include "BBLCloudServiceAgent.hpp"
|
||||
#include "BBLNetworkPlugin.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* CloudAgentProvider - Specifies which implementation to use for each agent type.
|
||||
*
|
||||
* - Orca: Native Orca cloud implementations (OrcaCloudServiceAgent)
|
||||
* - BBL: BBL DLL wrapper implementations (BBLCloudServiceAgent)
|
||||
*/
|
||||
enum class CloudAgentProvider { Orca, BBL };
|
||||
|
||||
static constexpr char ORCA_PRINTER_AGENT_ID[] = "orca";
|
||||
static constexpr char BBL_PRINTER_AGENT_ID[] = "bbl";
|
||||
|
||||
// Factory function type for creating printer agents
|
||||
using PrinterAgentFactory =
|
||||
std::function<std::shared_ptr<IPrinterAgent>(std::shared_ptr<ICloudServiceAgent> cloud_agent, const std::string& log_dir)>;
|
||||
|
||||
// Information about a registered printer agent
|
||||
struct PrinterAgentInfo
|
||||
{
|
||||
std::string id; // e.g., "orca", "bbl"
|
||||
std::string display_name; // e.g., "Orca Native", "Bambu Lab"
|
||||
PrinterAgentFactory factory; // Function to create the agent
|
||||
|
||||
PrinterAgentInfo(const std::string& id_, const std::string& display_name_, PrinterAgentFactory factory_)
|
||||
: id(id_), display_name(display_name_), factory(std::move(factory_))
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* NetworkAgentFactory - Factory for creating network agent instances
|
||||
*
|
||||
* This factory creates cloud agents and printer agents for the networking subsystem.
|
||||
* The architecture separates cloud services (authentication, project sync) from
|
||||
* printer communication (device discovery, print jobs).
|
||||
*
|
||||
* Startup flow:
|
||||
* 1. Call register_all_agents() during app initialization
|
||||
* 2. Cloud agent created at startup via create_agent_from_config()
|
||||
* 3. Printer agent created on-demand when a printer is selected
|
||||
*
|
||||
* Usage:
|
||||
* // At app startup (before any agent creation)
|
||||
* NetworkAgentFactory::register_all_agents();
|
||||
*
|
||||
* // Create NetworkAgent with cloud agent only
|
||||
* auto agent = create_agent_from_config(log_dir, app_config);
|
||||
*
|
||||
* // When printer is selected - create printer agent from registry
|
||||
* auto printer = NetworkAgentFactory::create_printer_agent_by_id("orca", cloud, log_dir);
|
||||
*/
|
||||
class NetworkAgentFactory
|
||||
{
|
||||
public:
|
||||
// ========================================================================
|
||||
// Printer Agent Registry
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Register all built-in printer agents.
|
||||
* Must be called once during application initialization, before any
|
||||
* calls to get_registered_printer_agents() or create_printer_agent_by_id().
|
||||
*/
|
||||
static void register_all_agents();
|
||||
|
||||
/**
|
||||
* Register a printer agent type
|
||||
*
|
||||
* @param id Unique identifier for the agent (e.g., "orca", "bbl")
|
||||
* @param display_name Human-readable name for UI
|
||||
* @param factory Factory function to create the agent
|
||||
* @return true if registration succeeded, false if already registered
|
||||
*/
|
||||
static bool register_printer_agent(const std::string& id, const std::string& display_name, PrinterAgentFactory factory);
|
||||
|
||||
/**
|
||||
* Check if an agent ID is registered
|
||||
*/
|
||||
static bool is_printer_agent_registered(const std::string& id);
|
||||
|
||||
/**
|
||||
* Get info about a registered agent
|
||||
*/
|
||||
static const PrinterAgentInfo* get_printer_agent_info(const std::string& id);
|
||||
|
||||
/**
|
||||
* Get all registered printer agents (for UI population)
|
||||
*/
|
||||
static std::vector<PrinterAgentInfo> get_registered_printer_agents();
|
||||
|
||||
/**
|
||||
* Create a printer agent by ID (using registry)
|
||||
*
|
||||
* Returns a cached instance if one exists for the given ID, otherwise
|
||||
* creates a new agent via the registered factory and caches it.
|
||||
*
|
||||
* @param id Agent ID to create
|
||||
* @param cloud_agent Cloud agent for token access
|
||||
* @param log_dir Directory for log files
|
||||
* @return Shared pointer to IPrinterAgent, or nullptr if ID not found
|
||||
*/
|
||||
static std::shared_ptr<IPrinterAgent> create_printer_agent_by_id(const std::string& id,
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent,
|
||||
const std::string& log_dir);
|
||||
|
||||
/**
|
||||
* Clear the printer agent cache.
|
||||
* Calls disconnect_printer() on each cached agent and releases all shared_ptrs.
|
||||
* Should be called during application shutdown before destroying the NetworkAgent.
|
||||
*/
|
||||
static void clear_printer_agent_cache();
|
||||
|
||||
// ========================================================================
|
||||
// Cloud Agent Factory
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Create a cloud service agent based on provider type.
|
||||
* Handles authentication, project sync, and other cloud services.
|
||||
*
|
||||
* @param provider Which implementation to use (Orca or BBL)
|
||||
* @param log_dir Directory for log files
|
||||
* @return Shared pointer to ICloudServiceAgent implementation
|
||||
*/
|
||||
static std::shared_ptr<ICloudServiceAgent> create_cloud_agent(CloudAgentProvider provider, const std::string& log_dir)
|
||||
{
|
||||
switch (provider) {
|
||||
case CloudAgentProvider::Orca: return std::make_shared<OrcaCloudServiceAgent>(log_dir);
|
||||
case CloudAgentProvider::BBL: {
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
if (!plugin.is_loaded()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!plugin.has_agent()) {
|
||||
plugin.create_agent(log_dir);
|
||||
}
|
||||
if (!plugin.has_agent()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_shared<BBLCloudServiceAgent>();
|
||||
}
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Factory is not instantiable
|
||||
NetworkAgentFactory() = delete;
|
||||
~NetworkAgentFactory() = delete;
|
||||
NetworkAgentFactory(const NetworkAgentFactory&) = delete;
|
||||
NetworkAgentFactory& operator=(const NetworkAgentFactory&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a NetworkAgent from AppConfig settings (main entry point)
|
||||
*
|
||||
* Creates a NetworkAgent with cloud agent only. The printer agent is created
|
||||
* separately when a printer is selected, via create_printer_agent_by_id().
|
||||
*
|
||||
* Cloud provider selection:
|
||||
* - use_orca_cloud=true → OrcaCloudServiceAgent (default)
|
||||
* - use_orca_cloud=false → BBLCloudServiceAgent (requires plugin)
|
||||
*
|
||||
* @param log_dir Directory for log files
|
||||
* @param app_config Application configuration object
|
||||
* @return NetworkAgent with cloud agent, or nullptr on failure
|
||||
*/
|
||||
std::unique_ptr<NetworkAgent> create_agent_from_config(const std::string& log_dir, AppConfig* app_config);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __NETWORK_AGENT_FACTORY_HPP__
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,359 @@
|
||||
#ifndef __ORCA_CLOUD_SERVICE_AGENT_HPP__
|
||||
#define __ORCA_CLOUD_SERVICE_AGENT_HPP__
|
||||
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
class wxSecretStore;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declaration
|
||||
class AppConfig;
|
||||
|
||||
// Constants for OAuth loopback server
|
||||
namespace auth_constants {
|
||||
constexpr int LOOPBACK_PORT = 41172;
|
||||
constexpr const char* LOOPBACK_PATH = "/callback";
|
||||
constexpr const char* TOKEN_PATH = "/auth/v1/token";
|
||||
constexpr const char* LOGOUT_PATH = "/auth/v1/logout";
|
||||
} // namespace auth_constants
|
||||
|
||||
// ============================================================================
|
||||
// Sync Protocol Data Structures (per Orca Cloud Sync Protocol Specification)
|
||||
// ============================================================================
|
||||
// Note: These may also be defined in OrcaNetwork.hpp - guards prevent redefinition
|
||||
|
||||
#ifndef ORCA_SYNC_STRUCTS_DEFINED
|
||||
#define ORCA_SYNC_STRUCTS_DEFINED
|
||||
|
||||
struct ProfileUpsert {
|
||||
std::string id;
|
||||
std::string name;
|
||||
nlohmann::json content;
|
||||
std::string updated_at;
|
||||
std::string created_at;
|
||||
};
|
||||
|
||||
struct SyncPullResponse {
|
||||
std::string next_cursor;
|
||||
std::vector<ProfileUpsert> upserts;
|
||||
std::vector<std::string> deletes;
|
||||
};
|
||||
|
||||
struct SyncPushResult {
|
||||
bool success;
|
||||
int http_code;
|
||||
std::string new_updated_at;
|
||||
ProfileUpsert server_version;
|
||||
bool server_deleted;
|
||||
std::string error_message;
|
||||
};
|
||||
|
||||
struct SyncState {
|
||||
std::string last_sync_timestamp;
|
||||
};
|
||||
|
||||
#endif // ORCA_SYNC_STRUCTS_DEFINED
|
||||
|
||||
/**
|
||||
* OrcaCloudServiceAgent - Native cloud service and authentication implementation for Orca Cloud.
|
||||
*
|
||||
* Implements the ICloudServiceAgent interface with:
|
||||
* - Full OAuth 2.0 PKCE authentication support
|
||||
* - Token storage via wxSecretStore with AES-256-GCM encrypted file fallback
|
||||
* - JWT expiry decoding and proactive token refresh
|
||||
* - Session management with thread-safe state access
|
||||
* - Settings synchronization (sync_pull, sync_push)
|
||||
* - Server connectivity management
|
||||
* - HTTP helpers with automatic token injection
|
||||
*
|
||||
* This class combines the functionality of the former OrcaAuthAgent and OrcaCloudServiceAgent.
|
||||
*/
|
||||
class OrcaCloudServiceAgent : public ICloudServiceAgent {
|
||||
public:
|
||||
// ========================================================================
|
||||
// Auth Session Types
|
||||
// ========================================================================
|
||||
struct SessionInfo {
|
||||
std::string access_token;
|
||||
std::string refresh_token;
|
||||
std::string user_id;
|
||||
std::string user_name;
|
||||
std::string user_nickname;
|
||||
std::string user_avatar;
|
||||
std::chrono::system_clock::time_point expires_at{};
|
||||
bool logged_in = false;
|
||||
};
|
||||
|
||||
struct PkceBundle {
|
||||
std::string verifier;
|
||||
std::string challenge;
|
||||
std::string state;
|
||||
std::string redirect;
|
||||
int loopback_port = auth_constants::LOOPBACK_PORT;
|
||||
};
|
||||
|
||||
using SessionHandler = std::function<bool(const std::string&)>;
|
||||
using OnLoginCompleteHandler = std::function<void(bool success, const std::string& user_id)>;
|
||||
|
||||
explicit OrcaCloudServiceAgent(std::string log_dir);
|
||||
~OrcaCloudServiceAgent() override;
|
||||
|
||||
// Configuration
|
||||
void configure_urls(AppConfig* app_config);
|
||||
void set_api_base_url(const std::string& url);
|
||||
void set_auth_base_url(const std::string& url);
|
||||
void set_use_encrypted_token_file(bool use);
|
||||
bool get_use_encrypted_token_file() const;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Lifecycle Methods
|
||||
// ========================================================================
|
||||
int init_log() override;
|
||||
int set_config_dir(std::string config_dir) override;
|
||||
int set_cert_file(std::string folder, std::string filename) override;
|
||||
int set_country_code(std::string country_code) override;
|
||||
int start() override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - User Session Management
|
||||
// ========================================================================
|
||||
int change_user(std::string user_info) override;
|
||||
bool is_user_login() override;
|
||||
int user_logout(bool request = false) override;
|
||||
std::string get_user_id() override;
|
||||
std::string get_user_name() override;
|
||||
std::string get_user_avatar() override;
|
||||
std::string get_user_nickname() override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Login UI Support
|
||||
// ========================================================================
|
||||
std::string build_login_cmd() override;
|
||||
std::string build_logout_cmd() override;
|
||||
std::string build_login_info() override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Token Access
|
||||
// ========================================================================
|
||||
std::string get_access_token() const override;
|
||||
std::string get_refresh_token() const override;
|
||||
bool ensure_token_fresh(const std::string& reason) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Server Connectivity
|
||||
// ========================================================================
|
||||
std::string get_cloud_service_host() override;
|
||||
std::string get_cloud_login_url(const std::string& language = "") override;
|
||||
int connect_server() override;
|
||||
bool is_server_connected() override;
|
||||
int refresh_connection() override;
|
||||
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;
|
||||
void enable_multi_machine(bool enable) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Settings Synchronization
|
||||
// ========================================================================
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets) override;
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int delete_setting(std::string setting_id) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Cloud User Services
|
||||
// ========================================================================
|
||||
int get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body) override;
|
||||
int check_user_task_report(int* task_id, bool* printable) override;
|
||||
int get_user_print_info(unsigned int* http_code, std::string* http_body) override;
|
||||
int get_user_tasks(TaskQueryParams params, std::string* http_body) override;
|
||||
int get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body) override;
|
||||
int get_task_plate_index(std::string task_id, int* plate_index) override;
|
||||
int get_user_info(int* identifier) override;
|
||||
int get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body) override;
|
||||
int get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json) override;
|
||||
int query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body) override;
|
||||
int modify_printer_name(std::string dev_id, std::string dev_name) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Model Mall & Publishing
|
||||
// ========================================================================
|
||||
int get_camera_url(std::string dev_id, std::function<void(std::string)> callback) override;
|
||||
int get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback) override;
|
||||
int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override;
|
||||
int get_model_publish_url(std::string* url) override;
|
||||
int get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn) override;
|
||||
int get_model_mall_home_url(std::string* url) override;
|
||||
int get_model_mall_detail_url(std::string* url, std::string id) override;
|
||||
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Analytics & Tracking
|
||||
// ========================================================================
|
||||
int track_enable(bool enable) override;
|
||||
int track_remove_files() override;
|
||||
int track_event(std::string evt_key, std::string content) override;
|
||||
int track_header(std::string header) override;
|
||||
int track_update_property(std::string name, std::string value, std::string type = "string") override;
|
||||
int track_get_property(std::string name, std::string& value, std::string type = "string") override;
|
||||
bool get_track_enable() override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Ratings & Reviews
|
||||
// ========================================================================
|
||||
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) override;
|
||||
int get_oss_config(std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error) override;
|
||||
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) override;
|
||||
int get_model_mall_rating_result(int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error) override;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - 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;
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Callbacks
|
||||
// ========================================================================
|
||||
int set_on_user_login_fn(OnUserLoginFn fn) override;
|
||||
int set_on_server_connected_fn(OnServerConnectedFn fn) override;
|
||||
int set_on_http_error_fn(OnHttpErrorFn fn) override;
|
||||
int set_get_country_code_fn(GetCountryCodeFn fn) override;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
|
||||
// Sync state management
|
||||
void load_sync_state();
|
||||
void save_sync_state();
|
||||
void clear_sync_state();
|
||||
const SyncState& get_sync_state() const { return sync_state; }
|
||||
|
||||
// ========================================================================
|
||||
// Additional Public Methods - Auth
|
||||
// ========================================================================
|
||||
void set_session_handler(SessionHandler handler);
|
||||
void set_on_login_complete_handler(OnLoginCompleteHandler handler);
|
||||
|
||||
const PkceBundle& pkce();
|
||||
void regenerate_pkce();
|
||||
|
||||
void persist_refresh_token(const std::string& token);
|
||||
bool load_refresh_token(std::string& out_token);
|
||||
void clear_refresh_token();
|
||||
|
||||
// Token refresh helpers
|
||||
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
|
||||
bool refresh_from_storage(const std::string& reason, bool async = false);
|
||||
bool refresh_now(const std::string& refresh_token, const std::string& reason, bool async = false);
|
||||
bool refresh_session_with_token(const std::string& refresh_token);
|
||||
|
||||
// Session state helpers
|
||||
bool set_user_session(const std::string& token,
|
||||
const std::string& user_id,
|
||||
const std::string& username,
|
||||
const std::string& name,
|
||||
const std::string& nickname,
|
||||
const std::string& avatar,
|
||||
const std::string& refresh_token = "");
|
||||
void clear_session();
|
||||
|
||||
private:
|
||||
// Sync protocol helpers
|
||||
int sync_pull(
|
||||
std::function<void(const SyncPullResponse&)> on_success,
|
||||
std::function<void(int http_code, const std::string& error)> on_error
|
||||
);
|
||||
|
||||
SyncPushResult sync_push(
|
||||
const std::string& profile_id,
|
||||
const std::string& name,
|
||||
const nlohmann::json& content,
|
||||
const std::string& original_updated_at = ""
|
||||
);
|
||||
|
||||
// HTTP request helpers
|
||||
int http_get(const std::string& path, std::string* response_body, unsigned int* http_code);
|
||||
int http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
|
||||
int http_put(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
|
||||
int http_delete(const std::string& path, std::string* response_body, unsigned int* http_code);
|
||||
std::map<std::string, std::string> data_headers();
|
||||
bool attempt_refresh_after_unauthorized(const std::string& reason);
|
||||
|
||||
// Auth HTTP helpers
|
||||
bool http_post_token(const std::string& body, std::string* response_body, unsigned int* http_code, const std::string& url = "");
|
||||
bool http_post_auth(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
|
||||
bool exchange_auth_code(const std::string& auth_code, const std::string& state, std::string& session_payload);
|
||||
void update_redirect_uri();
|
||||
void compute_fallback_path();
|
||||
bool decode_jwt_expiry(const std::string& token, std::chrono::system_clock::time_point& out_tp);
|
||||
bool should_refresh_locked(std::chrono::seconds skew) const;
|
||||
void invoke_user_login_callback(int online_login, bool login);
|
||||
|
||||
// Callback invocation
|
||||
void invoke_server_connected_callback(int return_code, int reason_code);
|
||||
void invoke_http_error_callback(unsigned http_code, const std::string& http_body);
|
||||
|
||||
// JSON helpers
|
||||
std::string map_to_json(const std::map<std::string, std::string>& map);
|
||||
void json_to_map(const std::string& json, std::map<std::string, std::string>& map);
|
||||
|
||||
// Member variables - configuration
|
||||
std::string log_dir;
|
||||
std::string config_dir;
|
||||
std::string api_base_url;
|
||||
std::string auth_base_url;
|
||||
std::string country_code;
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
std::map<std::string, std::string> auth_headers;
|
||||
mutable std::mutex headers_mutex;
|
||||
bool m_use_encrypted_token_file{false};
|
||||
|
||||
// Member variables - auth state
|
||||
PkceBundle pkce_bundle;
|
||||
std::string refresh_fallback_path;
|
||||
SessionHandler session_handler;
|
||||
OnLoginCompleteHandler on_login_complete_handler;
|
||||
SessionInfo session;
|
||||
mutable std::mutex session_mutex;
|
||||
|
||||
// Member variables - connection state
|
||||
bool is_connected{false};
|
||||
bool enable_track{false};
|
||||
bool multi_machine_enabled{false};
|
||||
|
||||
// Sync state
|
||||
SyncState sync_state;
|
||||
std::string sync_state_path;
|
||||
|
||||
// Callbacks
|
||||
OnUserLoginFn on_user_login_fn;
|
||||
OnServerConnectedFn on_server_connected_fn;
|
||||
OnHttpErrorFn on_http_error_fn;
|
||||
GetCountryCodeFn get_country_code_fn;
|
||||
QueueOnMainFn queue_on_main_fn;
|
||||
mutable std::mutex callback_mutex;
|
||||
|
||||
// Thread safety
|
||||
mutable std::recursive_mutex state_mutex;
|
||||
std::thread refresh_thread;
|
||||
std::atomic_bool refresh_running{false};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __ORCA_CLOUD_SERVICE_AGENT_HPP__
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "OrcaPrinterAgent.hpp"
|
||||
#include "NetworkAgentFactory.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const std::string OrcaPrinterAgent_VERSION = "0.0.1";
|
||||
|
||||
OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir))
|
||||
{
|
||||
}
|
||||
|
||||
OrcaPrinterAgent::~OrcaPrinterAgent() = default;
|
||||
|
||||
void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
m_cloud_agent = cloud;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Communication - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::disconnect_printer()
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Certificates - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::check_cert()
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
|
||||
{
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Discovery - Stub
|
||||
// ============================================================================
|
||||
|
||||
bool OrcaPrinterAgent::start_discovery(bool start, bool sending)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Binding - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::ping_bind(std::string ping_code)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::bind(
|
||||
std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::unbind(std::string dev_id)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::request_bind_ticket(std::string* ticket)
|
||||
{
|
||||
if (ticket)
|
||||
*ticket = "";
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_server_callback(OnServerErrFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_server_err_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Machine Selection
|
||||
// ============================================================================
|
||||
|
||||
std::string OrcaPrinterAgent::get_user_selected_machine()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
return selected_machine;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_user_selected_machine(std::string dev_id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
selected_machine = dev_id;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Information
|
||||
// ============================================================================
|
||||
AgentInfo OrcaPrinterAgent::get_agent_info_static()
|
||||
{
|
||||
return AgentInfo{ORCA_PRINTER_AGENT_ID, "Orca", OrcaPrinterAgent_VERSION, "Orca Printer Communication Protocol Agent"};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Print Job Operations - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::start_local_print_with_record(PrintParams params,
|
||||
OnUpdateStatusFn update_fn,
|
||||
WasCancelledFn cancel_fn,
|
||||
OnWaitFn wait_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
|
||||
{
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callback Registration
|
||||
// ============================================================================
|
||||
|
||||
int OrcaPrinterAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_ssdp_msg_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_on_printer_connected_fn(OnPrinterConnectedFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_printer_connected_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_on_subscribe_failure_fn(GetSubscribeFailureFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_subscribe_failure_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_on_message_fn(OnMessageFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_message_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_on_user_message_fn(OnMessageFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_user_message_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_on_local_connect_fn(OnLocalConnectedFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_local_connect_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_on_local_message_fn(OnMessageFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
on_local_message_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
queue_on_main_fn = fn;
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifndef __ORCA_PRINTER_AGENT_HPP__
|
||||
#define __ORCA_PRINTER_AGENT_HPP__
|
||||
|
||||
#include "IPrinterAgent.hpp"
|
||||
#include "ICloudServiceAgent.hpp"
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/**
|
||||
* OrcaPrinterAgent - Stub implementation for printer operations.
|
||||
*
|
||||
* All printer-related operations are currently stubs that return success.
|
||||
* Actual printer connectivity requires the BBL SDK or future Orca implementation.
|
||||
*/
|
||||
class OrcaPrinterAgent : public IPrinterAgent {
|
||||
public:
|
||||
explicit OrcaPrinterAgent(std::string log_dir);
|
||||
~OrcaPrinterAgent() override;
|
||||
|
||||
// ========================================================================
|
||||
// IPrinterAgent Interface Implementation
|
||||
// ========================================================================
|
||||
|
||||
void set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud) override;
|
||||
|
||||
// Communication
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override;
|
||||
int disconnect_printer() override;
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override;
|
||||
|
||||
// Certificates
|
||||
int check_cert() override;
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override;
|
||||
|
||||
// Discovery
|
||||
bool start_discovery(bool start, bool sending) override;
|
||||
|
||||
// Binding
|
||||
int ping_bind(std::string ping_code) override;
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
|
||||
int unbind(std::string dev_id) override;
|
||||
int request_bind_ticket(std::string* ticket) override;
|
||||
int set_server_callback(OnServerErrFn fn) override;
|
||||
|
||||
// Machine Selection
|
||||
std::string get_user_selected_machine() override;
|
||||
int set_user_selected_machine(std::string dev_id) override;
|
||||
|
||||
/**
|
||||
* Get agent information.
|
||||
*
|
||||
* @return AgentInfo struct containing agent identification and descriptive information
|
||||
*/
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
// Print Job Operations
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override;
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override;
|
||||
|
||||
// Callbacks
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override;
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override;
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override;
|
||||
int set_on_message_fn(OnMessageFn fn) override;
|
||||
int set_on_user_message_fn(OnMessageFn fn) override;
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override;
|
||||
int set_on_local_message_fn(OnMessageFn fn) override;
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override;
|
||||
|
||||
private:
|
||||
std::string log_dir;
|
||||
std::string selected_machine;
|
||||
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
|
||||
|
||||
// Callbacks
|
||||
OnMsgArrivedFn on_ssdp_msg_fn;
|
||||
OnPrinterConnectedFn on_printer_connected_fn;
|
||||
GetSubscribeFailureFn on_subscribe_failure_fn;
|
||||
OnMessageFn on_message_fn;
|
||||
OnMessageFn on_user_message_fn;
|
||||
OnLocalConnectedFn on_local_connect_fn;
|
||||
OnMessageFn on_local_message_fn;
|
||||
QueueOnMainFn queue_on_main_fn;
|
||||
OnServerErrFn on_server_err_fn;
|
||||
|
||||
mutable std::mutex state_mutex;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __ORCA_PRINTER_AGENT_HPP__
|
||||
@@ -849,7 +849,7 @@ void PresetUpdater::priv::sync_plugins(std::string http_url, std::string plugin_
|
||||
BOOST_LOG_TRIVIAL(info) << "non need to sync plugins for there is no plugins currently.";
|
||||
return;
|
||||
}
|
||||
std::string curr_version = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BBL::get_latest_network_version();
|
||||
std::string curr_version = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : get_latest_network_version();
|
||||
std::string using_version = curr_version.substr(0, 9) + "00";
|
||||
|
||||
std::string cached_version;
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
#include "QidiPrinterAgent.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
// Check whether any visible, compatible base preset in the collection has the given filament_id.
|
||||
bool has_visible_base_preset(const PresetCollection& filaments, const std::string& filament_id)
|
||||
{
|
||||
for (const auto& p : filaments.get_presets()) {
|
||||
if (p.is_visible && p.is_compatible
|
||||
&& filaments.get_preset_base(p) == &p
|
||||
&& p.filament_id == filament_id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
const std::string QidiPrinterAgent_VERSION = "0.0.1";
|
||||
|
||||
QidiPrinterAgent::QidiPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir))
|
||||
{
|
||||
}
|
||||
|
||||
AgentInfo QidiPrinterAgent::get_agent_info_static()
|
||||
{
|
||||
return AgentInfo{"qidi", "Qidi", QidiPrinterAgent_VERSION, "Qidi printer agent"};
|
||||
}
|
||||
|
||||
bool QidiPrinterAgent::fetch_filament_info(std::string dev_id)
|
||||
{
|
||||
std::string error;
|
||||
|
||||
// 1. Fetch device info and infer series_id
|
||||
std::string series_id;
|
||||
{
|
||||
MoonrakerDeviceInfo info;
|
||||
if (fetch_device_info(device_info.base_url, device_info.api_key, info, error)) {
|
||||
series_id = infer_series_id(info.model_id, info.dev_name);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fetch filament dictionary
|
||||
QidiFilamentDict dict;
|
||||
if (!fetch_filament_dict(device_info.base_url, device_info.api_key, dict, error)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error;
|
||||
}
|
||||
|
||||
// 3. Fetch slot info and build AmsTrayData directly
|
||||
std::vector<AmsTrayData> trays;
|
||||
int box_count = 0;
|
||||
if (!fetch_slot_info(device_info.base_url, device_info.api_key, dict, series_id, trays, box_count, error)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Build the AMS payload
|
||||
build_ams_payload(box_count, box_count * 4 - 1, trays);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QidiPrinterAgent::fetch_slot_info(const std::string& base_url,
|
||||
const std::string& api_key,
|
||||
const QidiFilamentDict& dict,
|
||||
const std::string& series_id,
|
||||
std::vector<AmsTrayData>& trays,
|
||||
int& box_count,
|
||||
std::string& error)
|
||||
{
|
||||
std::string url = join_url(base_url, "/printer/objects/query?save_variables=variables");
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
url += "&box_stepper%20slot" + std::to_string(i) + "=runout_button";
|
||||
}
|
||||
|
||||
std::string response_body;
|
||||
bool success = false;
|
||||
std::string http_error;
|
||||
|
||||
auto http = Http::get(url);
|
||||
if (!api_key.empty()) {
|
||||
http.header("X-Api-Key", api_key);
|
||||
}
|
||||
http.timeout_connect(5)
|
||||
.timeout_max(10)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
if (status == 200) {
|
||||
response_body = body;
|
||||
success = true;
|
||||
} else {
|
||||
http_error = "HTTP error: " + std::to_string(status);
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string err, unsigned status) {
|
||||
http_error = err;
|
||||
if (status > 0) {
|
||||
http_error += " (HTTP " + std::to_string(status) + ")";
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if (!success) {
|
||||
error = http_error.empty() ? "Connection failed" : http_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
|
||||
if (json.is_discarded()) {
|
||||
error = "Invalid JSON response";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!json.contains("result") || !json["result"].contains("status") || !json["result"]["status"].contains("save_variables") ||
|
||||
!json["result"]["status"]["save_variables"].contains("variables")) {
|
||||
error = "Unexpected JSON structure";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& variables = json["result"]["status"]["save_variables"]["variables"];
|
||||
auto& status = json["result"]["status"];
|
||||
|
||||
box_count = variables.value("box_count", 1);
|
||||
if (box_count < 0) {
|
||||
box_count = 0;
|
||||
}
|
||||
|
||||
const int max_slots = box_count * 4;
|
||||
trays.clear();
|
||||
trays.reserve(max_slots);
|
||||
|
||||
// Lambda to build setting_id from slot data
|
||||
auto build_setting_id = [&](int filament_type_idx, int vendor_type, const std::string& tray_type) {
|
||||
const int vendor = (vendor_type == 1) ? 1 : 0;
|
||||
if (is_numeric(series_id) && filament_type_idx > 0) {
|
||||
return "QD_" + series_id + "_" + std::to_string(vendor) + "_" + std::to_string(filament_type_idx);
|
||||
}
|
||||
return map_filament_type_to_setting_id(tray_type);
|
||||
};
|
||||
|
||||
for (int i = 0; i < max_slots; ++i) {
|
||||
AmsTrayData tray;
|
||||
tray.slot_index = i;
|
||||
|
||||
// Read slot variables
|
||||
const int color_index = variables.value("color_slot" + std::to_string(i), 1);
|
||||
const int filament_type = variables.value("filament_slot" + std::to_string(i), 1);
|
||||
const int vendor_type = variables.value("vendor_slot" + std::to_string(i), 0);
|
||||
|
||||
// Check filament presence via runout sensor
|
||||
std::string box_stepper_key = "box_stepper slot" + std::to_string(i);
|
||||
tray.has_filament = false;
|
||||
if (status.contains(box_stepper_key)) {
|
||||
auto& box_stepper = status[box_stepper_key];
|
||||
if (box_stepper.contains("runout_button") && !box_stepper["runout_button"].is_null()) {
|
||||
int runout_button = box_stepper["runout_button"].template get<int>();
|
||||
tray.has_filament = (runout_button == 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (tray.has_filament) {
|
||||
// Look up filament type name from dictionary
|
||||
std::string filament_name = "PLA";
|
||||
auto filament_it = dict.filaments.find(filament_type);
|
||||
if (filament_it != dict.filaments.end()) {
|
||||
filament_name = filament_it->second;
|
||||
}
|
||||
tray.tray_type = normalize_filament_type(filament_name);
|
||||
|
||||
// Try Qidi-specific setting ID first; fall back to visible preset by type
|
||||
std::string setting_id = build_setting_id(filament_type, vendor_type, tray.tray_type);
|
||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||
if (!bundle) {
|
||||
tray.tray_info_idx = setting_id;
|
||||
} else if (!setting_id.empty() && has_visible_base_preset(bundle->filaments, setting_id)) {
|
||||
tray.tray_info_idx = setting_id;
|
||||
} else {
|
||||
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
|
||||
}
|
||||
|
||||
// Look up color from dictionary
|
||||
auto color_it = dict.colors.find(color_index);
|
||||
if (color_it != dict.colors.end()) {
|
||||
tray.tray_color = color_it->second;
|
||||
} else {
|
||||
tray.tray_color = "FFFFFFFF";
|
||||
}
|
||||
}
|
||||
|
||||
trays.push_back(tray);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QidiPrinterAgent::fetch_filament_dict(const std::string& base_url,
|
||||
const std::string& api_key,
|
||||
QidiFilamentDict& dict,
|
||||
std::string& error) const
|
||||
{
|
||||
std::string url = join_url(base_url, "/server/files/config/officiall_filas_list.cfg");
|
||||
|
||||
std::string response_body;
|
||||
bool success = false;
|
||||
std::string http_error;
|
||||
|
||||
auto http = Http::get(url);
|
||||
if (!api_key.empty()) {
|
||||
http.header("X-Api-Key", api_key);
|
||||
}
|
||||
http.timeout_connect(5)
|
||||
.timeout_max(10)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
if (status == 200) {
|
||||
response_body = body;
|
||||
success = true;
|
||||
} else {
|
||||
http_error = "HTTP error: " + std::to_string(status);
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string err, unsigned status) {
|
||||
http_error = err;
|
||||
if (status > 0) {
|
||||
http_error += " (HTTP " + std::to_string(status) + ")";
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if (!success) {
|
||||
error = http_error.empty() ? "Connection failed" : http_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
dict.colors.clear();
|
||||
dict.filaments.clear();
|
||||
parse_ini_section(response_body, "colordict", dict.colors);
|
||||
parse_filament_sections(response_body, dict.filaments);
|
||||
|
||||
return !dict.colors.empty();
|
||||
}
|
||||
|
||||
void QidiPrinterAgent::parse_ini_section(const std::string& content, const std::string& section_name, std::map<int, std::string>& result)
|
||||
{
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
bool in_section = false;
|
||||
std::string section_header = "[" + section_name + "]";
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
boost::trim(line);
|
||||
if (!line.empty() && line[0] == '[') {
|
||||
in_section = (line == section_header);
|
||||
continue;
|
||||
}
|
||||
if (line.empty() || line[0] == '#' || line[0] == ';') {
|
||||
continue;
|
||||
}
|
||||
if (in_section) {
|
||||
auto pos = line.find('=');
|
||||
if (pos != std::string::npos) {
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string value = line.substr(pos + 1);
|
||||
boost::trim(key);
|
||||
boost::trim(value);
|
||||
try {
|
||||
int index = std::stoi(key);
|
||||
result[index] = value;
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QidiPrinterAgent::parse_filament_sections(const std::string& content, std::map<int, std::string>& result)
|
||||
{
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
int current_fila_index = -1;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
boost::trim(line);
|
||||
if (!line.empty() && line[0] == '[') {
|
||||
current_fila_index = -1;
|
||||
if (line.size() > 5 && line.substr(0, 5) == "[fila" && line.back() == ']') {
|
||||
std::string num_str = line.substr(5, line.size() - 6);
|
||||
try {
|
||||
current_fila_index = std::stoi(num_str);
|
||||
} catch (...) {
|
||||
current_fila_index = -1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (line.empty() || line[0] == '#' || line[0] == ';') {
|
||||
continue;
|
||||
}
|
||||
if (current_fila_index > 0) {
|
||||
auto pos = line.find('=');
|
||||
if (pos != std::string::npos) {
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string value = line.substr(pos + 1);
|
||||
boost::trim(key);
|
||||
boost::trim(value);
|
||||
if (key == "filament") {
|
||||
result[current_fila_index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string QidiPrinterAgent::map_filament_type_to_setting_id(const std::string& filament_type)
|
||||
{
|
||||
const std::string upper = trim_and_upper(filament_type);
|
||||
|
||||
if (upper == "PLA") {
|
||||
return "QD_1_0_1";
|
||||
}
|
||||
if (upper == "ABS") {
|
||||
return "QD_1_0_11";
|
||||
}
|
||||
if (upper == "PETG") {
|
||||
return "QD_1_0_41";
|
||||
}
|
||||
if (upper == "TPU") {
|
||||
return "QD_1_0_50";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string QidiPrinterAgent::normalize_model_key(std::string value)
|
||||
{
|
||||
boost::algorithm::to_lower(value);
|
||||
std::string normalized;
|
||||
normalized.reserve(value.size());
|
||||
for (unsigned char c : value) {
|
||||
if (std::isalnum(c)) {
|
||||
normalized.push_back(static_cast<char>(c));
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
std::string QidiPrinterAgent::infer_series_id(const std::string& model_id, const std::string& dev_name)
|
||||
{
|
||||
std::string source = model_id.empty() ? dev_name : model_id;
|
||||
boost::trim(source);
|
||||
if (source.empty()) {
|
||||
return "";
|
||||
}
|
||||
if (is_numeric(source)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
const std::string key = normalize_model_key(source);
|
||||
if (key.find("q2") != std::string::npos) {
|
||||
return "1";
|
||||
}
|
||||
if (key.find("xmax") != std::string::npos && key.find("4") != std::string::npos) {
|
||||
return "3";
|
||||
}
|
||||
if ((key.find("xplus") != std::string::npos || key.find("plus") != std::string::npos) && key.find("4") != std::string::npos) {
|
||||
return "0";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string QidiPrinterAgent::normalize_filament_type(const std::string& filament_type)
|
||||
{
|
||||
const std::string upper = trim_and_upper(filament_type);
|
||||
|
||||
if (upper.find("PLA") != std::string::npos)
|
||||
return "PLA";
|
||||
if (upper.find("ABS") != std::string::npos)
|
||||
return "ABS";
|
||||
if (upper.find("PETG") != std::string::npos)
|
||||
return "PETG";
|
||||
if (upper.find("TPU") != std::string::npos)
|
||||
return "TPU";
|
||||
if (upper.find("ASA") != std::string::npos)
|
||||
return "ASA";
|
||||
if (upper.find("PA") != std::string::npos || upper.find("NYLON") != std::string::npos)
|
||||
return "PA";
|
||||
if (upper.find("PC") != std::string::npos)
|
||||
return "PC";
|
||||
if (upper.find("PVA") != std::string::npos)
|
||||
return "PVA";
|
||||
|
||||
return upper;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,52 @@
|
||||
#ifndef __QIDI_PRINTER_AGENT_HPP__
|
||||
#define __QIDI_PRINTER_AGENT_HPP__
|
||||
|
||||
#include "MoonrakerPrinterAgent.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class QidiPrinterAgent final : public MoonrakerPrinterAgent
|
||||
{
|
||||
public:
|
||||
explicit QidiPrinterAgent(std::string log_dir);
|
||||
~QidiPrinterAgent() override = default;
|
||||
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
// Override filament sync (Qidi-specific implementation)
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
|
||||
private:
|
||||
struct QidiFilamentDict
|
||||
{
|
||||
std::map<int, std::string> colors;
|
||||
std::map<int, std::string> filaments;
|
||||
};
|
||||
|
||||
// Qidi-specific methods
|
||||
bool fetch_slot_info(const std::string& base_url,
|
||||
const std::string& api_key,
|
||||
const QidiFilamentDict& dict,
|
||||
const std::string& series_id,
|
||||
std::vector<AmsTrayData>& trays,
|
||||
int& box_count,
|
||||
std::string& error);
|
||||
bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const;
|
||||
std::string normalize_filament_type(const std::string& filament_type);
|
||||
std::string infer_series_id(const std::string& model_id, const std::string& dev_name);
|
||||
std::string normalize_model_key(std::string value);
|
||||
|
||||
// Static helpers
|
||||
static void parse_ini_section(const std::string& content, const std::string& section_name, std::map<int, std::string>& result);
|
||||
static void parse_filament_sections(const std::string& content, std::map<int, std::string>& result);
|
||||
static std::string map_filament_type_to_setting_id(const std::string& filament_type);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "SnapmakerPrinterAgent.hpp"
|
||||
#include "Http.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* SNAPMAKER_AGENT_VERSION = "0.0.1";
|
||||
|
||||
// Safely access a parallel array by index, returning a fallback if out of bounds.
|
||||
template<typename T>
|
||||
T safe_at(const std::vector<T>& vec, int index, const T& fallback)
|
||||
{
|
||||
return (index >= 0 && index < static_cast<int>(vec.size())) ? vec[index] : fallback;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
SnapmakerPrinterAgent::SnapmakerPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) {}
|
||||
|
||||
AgentInfo SnapmakerPrinterAgent::get_agent_info_static()
|
||||
{
|
||||
return AgentInfo{"snapmaker", "Snapmaker", SNAPMAKER_AGENT_VERSION, "Snapmaker printer agent"};
|
||||
}
|
||||
|
||||
std::string SnapmakerPrinterAgent::combine_filament_type(const std::string& type, const std::string& sub_type)
|
||||
{
|
||||
const std::string base = trim_and_upper(type);
|
||||
const std::string sub = trim_and_upper(sub_type);
|
||||
|
||||
if (base.empty())
|
||||
return "PLA";
|
||||
|
||||
if (sub.empty() || sub == "NONE")
|
||||
return base;
|
||||
|
||||
if (sub == "CF")
|
||||
return base + "-CF";
|
||||
if (sub == "GF")
|
||||
return base + "-GF";
|
||||
if (sub == "SILK")
|
||||
return base + " SILK";
|
||||
if (sub == "SNAPSPEED" || sub == "HS")
|
||||
return base + " HIGH SPEED";
|
||||
|
||||
// Unrecognized sub-type (brand names like Polylite, Basic, etc.) -- use base type only
|
||||
return base;
|
||||
}
|
||||
|
||||
bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id)
|
||||
{
|
||||
std::string url = join_url(device_info.base_url, "/printer/objects/query?print_task_config&filament_detect");
|
||||
|
||||
std::string response_body;
|
||||
bool success = false;
|
||||
std::string http_error;
|
||||
|
||||
auto http = Http::get(url);
|
||||
if (!device_info.api_key.empty()) {
|
||||
http.header("X-Api-Key", device_info.api_key);
|
||||
}
|
||||
http.timeout_connect(5)
|
||||
.timeout_max(10)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
if (status == 200) {
|
||||
response_body = body;
|
||||
success = true;
|
||||
} else {
|
||||
http_error = "HTTP error: " + std::to_string(status);
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string err, unsigned status) {
|
||||
http_error = err;
|
||||
if (status > 0) {
|
||||
http_error += " (HTTP " + std::to_string(status) + ")";
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if (!success) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: HTTP request failed: " << http_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto json = nlohmann::json::parse(response_body, nullptr, false, true);
|
||||
if (json.is_discarded()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Invalid JSON response";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Navigate to result.status.print_task_config
|
||||
if (!json.contains("result") || !json["result"].contains("status") ||
|
||||
!json["result"]["status"].contains("print_task_config")) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SnapmakerPrinterAgent::fetch_filament_info: Missing print_task_config in response";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& ptc = json["result"]["status"]["print_task_config"];
|
||||
|
||||
// Read parallel arrays from print_task_config
|
||||
auto filament_exist = ptc.value("filament_exist", std::vector<bool>{});
|
||||
auto filament_type = ptc.value("filament_type", std::vector<std::string>{});
|
||||
auto filament_sub_type = ptc.value("filament_sub_type", std::vector<std::string>{});
|
||||
auto filament_color = ptc.value("filament_color_rgba", std::vector<std::string>{});
|
||||
|
||||
const int slot_count = static_cast<int>(filament_exist.size());
|
||||
if (slot_count == 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << "SnapmakerPrinterAgent::fetch_filament_info: No filament slots reported";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read NFC filament_detect data for temperature info (optional)
|
||||
nlohmann::json nfc_info;
|
||||
if (json["result"]["status"].contains("filament_detect") &&
|
||||
json["result"]["status"]["filament_detect"].contains("info")) {
|
||||
nfc_info = json["result"]["status"]["filament_detect"]["info"];
|
||||
}
|
||||
|
||||
static const std::string empty_str;
|
||||
static const std::string default_color = "FFFFFFFF";
|
||||
|
||||
std::vector<AmsTrayData> trays;
|
||||
trays.reserve(slot_count);
|
||||
|
||||
for (int i = 0; i < slot_count; ++i) {
|
||||
AmsTrayData tray;
|
||||
tray.slot_index = i;
|
||||
tray.has_filament = filament_exist[i];
|
||||
|
||||
if (tray.has_filament) {
|
||||
tray.tray_type = combine_filament_type(safe_at(filament_type, i, empty_str),
|
||||
safe_at(filament_sub_type, i, empty_str));
|
||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||
tray.tray_info_idx = bundle
|
||||
? bundle->filaments.filament_id_by_type(tray.tray_type)
|
||||
: map_filament_type_to_generic_id(tray.tray_type);
|
||||
tray.tray_color = safe_at(filament_color, i, default_color);
|
||||
|
||||
// Extract NFC temperature data if available
|
||||
if (nfc_info.is_array() && i < static_cast<int>(nfc_info.size()) && nfc_info[i].is_object()) {
|
||||
auto& nfc_slot = nfc_info[i];
|
||||
std::string vendor = nfc_slot.value("VENDOR", "NONE");
|
||||
if (vendor != "NONE" && !vendor.empty()) {
|
||||
tray.bed_temp = nfc_slot.value("BED_TEMP", 0);
|
||||
tray.nozzle_temp = nfc_slot.value("FIRST_LAYER_TEMP", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trays.emplace_back(std::move(tray));
|
||||
}
|
||||
|
||||
build_ams_payload(1, slot_count - 1, trays);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "MoonrakerPrinterAgent.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SnapmakerPrinterAgent final : public MoonrakerPrinterAgent
|
||||
{
|
||||
public:
|
||||
explicit SnapmakerPrinterAgent(std::string log_dir);
|
||||
~SnapmakerPrinterAgent() override = default;
|
||||
|
||||
static AgentInfo get_agent_info_static();
|
||||
AgentInfo get_agent_info() override { return get_agent_info_static(); }
|
||||
|
||||
bool fetch_filament_info(std::string dev_id) override;
|
||||
|
||||
private:
|
||||
// Combine filament_type + filament_sub_type into a unified type string
|
||||
static std::string combine_filament_type(const std::string& type, const std::string& sub_type);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -10,7 +10,7 @@
|
||||
extern std::string g_log_folder;
|
||||
extern std::string g_log_start_time;
|
||||
|
||||
namespace BBL {
|
||||
namespace Slic3r {
|
||||
|
||||
#define BAMBU_NETWORK_SUCCESS 0
|
||||
#define BAMBU_NETWORK_ERR_INVALID_HANDLE -1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
using namespace BBL;
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("extract_base_version", "[BambuNetworking]") {
|
||||
SECTION("version without suffix returns unchanged") {
|
||||
|
||||
Reference in New Issue
Block a user