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:
SoftFever
2026-02-03 15:42:46 +08:00
committed by GitHub
75 changed files with 13010 additions and 1988 deletions
+7 -51
View File
@@ -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/`
+927
View File
@@ -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>
+477
View File
@@ -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())
+7
View File
@@ -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);
+1
View File
@@ -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
+46 -5
View File
@@ -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",
+8 -1
View File
@@ -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
+113 -9
View File
@@ -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
+7
View File
@@ -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 "
+73
View File
@@ -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
+9
View File
@@ -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
+20
View File
@@ -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
+1 -1
View File
@@ -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
+6 -3
View File
@@ -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_);
}
};
}
+24
View File
@@ -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();
+17 -2
View File
@@ -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
View File
@@ -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();
+8
View File
@@ -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;
+56 -1
View File
@@ -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
View File
@@ -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);
+6 -6
View File
@@ -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 {
+11 -11
View File
@@ -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){
+1 -1
View File
@@ -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;
+9 -9
View File
@@ -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."));
+1 -1
View File
@@ -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 };
+2
View File
@@ -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 -2
View File
@@ -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) {
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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
+88 -2
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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) {
+9 -3
View File
@@ -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);
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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;
+4 -4
View File
@@ -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);
}
}
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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();
+5
View File
@@ -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);
+6 -6
View File
@@ -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;
+5 -5
View File
@@ -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);
+71 -18
View File
@@ -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);
+1
View File
@@ -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;
+14 -11
View File
@@ -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);
}
+861
View File
@@ -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
+136
View File
@@ -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__
+790
View File
@@ -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
+515
View File
@@ -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__
+372
View File
@@ -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
+86
View File
@@ -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__
+4 -4
View File
@@ -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();
+78
View File
@@ -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;
}
+3
View File
@@ -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();
+477
View File
@@ -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__
+260
View File
@@ -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
+203
View File
@@ -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
File diff suppressed because it is too large Load Diff
+50 -209
View File
@@ -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
+183
View File
@@ -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
+187
View File
@@ -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
+359
View File
@@ -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__
+222
View File
@@ -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
+100
View File
@@ -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__
+1 -1
View File
@@ -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;
+401
View File
@@ -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
+52
View File
@@ -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
+162
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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") {