This commit is contained in:
SoftFever 2026-01-23 17:05:56 +08:00
parent 12f51f906e
commit 9703a6b4d7
60 changed files with 13396 additions and 1785 deletions

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>

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);

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

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
@ -3395,6 +3395,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",

View file

@ -52,7 +52,8 @@
#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 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"

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 "

View file

@ -6,6 +6,7 @@
#include <cassert>
#include <ctime>
#include <cstdio>
#include <cctype>
#ifdef _MSC_VER
#include <map>
@ -230,5 +231,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

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

View file

@ -598,6 +598,24 @@ 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/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

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();

View file

@ -255,6 +255,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 +858,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);

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;
@ -2233,6 +2237,8 @@ GUI_App::~GUI_App()
}
StaticBambuLib::release();
BBLNetworkPlugin::shutdown();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
}
@ -3323,7 +3329,7 @@ __retry:
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);
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);
@ -3370,7 +3376,12 @@ __retry:
//std::string data_dir = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data();
std::string data_directory = data_dir();
m_agent = new Slic3r::NetworkAgent(data_directory);
// 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);
@ -3460,6 +3471,82 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
));
}
void GUI_App::switch_printer_agent(const std::string& agent_id)
{
if (!m_agent) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no agent exists";
return;
}
// Use registry to validate and create agent
// If empty, use default
std::string effective_agent_id = agent_id.empty() ? NetworkAgentFactory::get_default_printer_agent_id() : agent_id;
// 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 && m_agent->get_printer_agent())
current_agent_id = m_agent->get_printer_agent()->get_agent_info().id;
if (!current_agent_id.empty() && current_agent_id == effective_agent_id) {
return;
}
std::string log_dir = data_dir();
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent();
if (!cloud_agent) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no cloud agent available";
return;
}
// 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);
// Update dependent managers
if (m_device_manager) {
m_device_manager->set_agent(m_agent);
// If there's a selected machine that was deferred due to no printer agent,
// trigger a connection now that the agent is ready
MachineObject* selected = m_device_manager->get_selected_machine();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": checking for deferred connection - selected="
<< (selected ? selected->get_dev_id() : "null");
if (selected) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": selected machine - is_lan_mode=" << selected->is_lan_mode_printer()
<< " is_connected=" << selected->is_connected();
}
if (selected && selected->is_lan_mode_printer() && !selected->is_connected()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": connecting deferred LAN machine dev_id=" << selected->get_dev_id();
#if !BBL_RELEASE_TO_PUBLIC
selected->connect(app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
selected->connect(selected->local_use_ssl_for_mqtt);
#endif
selected->set_lan_mode_connection_state(true);
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": m_device_manager is null, cannot check for deferred connection";
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
}
bool GUI_App::dark_mode()
{
#ifdef SUPPORT_DARK_MODE
@ -4283,10 +4370,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 +5726,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 +5755,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 +5781,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 +5843,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 +5894,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 +6004,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();

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(const std::string& agent_id);
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);

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

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);

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 {

View file

@ -197,7 +197,7 @@ 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;
@ -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){

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;
@ -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."));

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>

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) {

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;

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

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,55 @@ 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);
}
// Set initial selection based on current config value or default
const std::string current_agent = m_config->opt_string("printer_agent");
std::string selected_agent = current_agent;
if (selected_agent.empty()) {
selected_agent = NetworkAgentFactory::get_default_printer_agent_id();
}
// Verify selected agent is valid
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 = NetworkAgentFactory::get_default_printer_agent_id();
}
// Set default value for the enum (using the index)
auto def_it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
if (def_it != agents.end()) {
size_t default_idx = std::distance(agents.begin(), def_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 +752,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;

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();

View file

@ -2371,7 +2371,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 +3168,49 @@ 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) {
agent->fetch_filament_info(obj->get_dev_id());
}
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;
@ -3285,7 +3330,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 +3367,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()) {
@ -15134,7 +15187,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 = "<< "";

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);

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;

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);
}
}

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);

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) {
update_printer_agent_if_needed();
}
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);
@ -2129,6 +2134,28 @@ void Tab::on_presets_changed()
wxGetApp().plater()->update_project_dirty_from_presets();
}
void Tab::update_printer_agent_if_needed()
{
std::string agent_id = "orca";
if (m_preset_bundle) {
if (wxGetApp().preset_bundle->is_bbl_vendor()) {
agent_id = "bbl";
} else if (wxGetApp().preset_bundle->is_qidi_vendor()) {
agent_id = "qidi";
}
}
const DynamicPrintConfig& config = m_preset_bundle->printers.get_edited_preset().config;
if (config.has("printer_agent")) {
std::string value = config.option<ConfigOptionString>("printer_agent")->value;
if (!value.empty()) {
agent_id = value;
}
}
// Switch agent in GUI_App
wxGetApp().switch_printer_agent(agent_id);
}
void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup)
{
auto description_line = [this](wxWindow* parent) {

View file

@ -437,6 +437,7 @@ protected:
// return true if cancelled
bool tree_sel_change_delayed(wxCommandEvent& event);
void on_presets_changed();
void update_printer_agent_if_needed();
void build_preset_description_line(ConfigOptionsGroup* optgroup);
void update_preset_description_line();
void update_frequently_changed_parameters();

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;

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);

View file

@ -75,15 +75,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 +221,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,28 +264,97 @@ void ZUserLogin::OnFullScreenChanged(wxWebViewEvent &evt)
void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
{
wxString str_input = evt.GetString();
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] OnScriptMessage received: " << str_input.ToStdString();
try {
json j = json::parse(into_u8(str_input));
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Parsed JSON successfully";
wxString strCmd = j["command"];
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Command: " << strCmd.ToStdString();
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")
{
m_AutotestToken = j["data"]["token"];
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Stored autotest_token";
}
if (strCmd == "user_login") {
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Processing user_login command";
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] User data: " << j["data"].dump();
j["data"]["autotest_token"] = m_AutotestToken;
wxGetApp().handle_script_message(j.dump());
Close();
std::string message_json = j.dump();
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Calling handle_script_message with: " << message_json;
// End modal dialog first to unblock event loop before processing callbacks
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Ending modal dialog";
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]() {
BOOST_LOG_TRIVIAL(debug) << "[WebUserLoginDialog] Processing login message after modal ended";
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();
BOOST_LOG_TRIVIAL(debug) << "thirdparty_login: get_localhost_url";
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());
@ -300,6 +365,8 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
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);

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;

View file

@ -0,0 +1,864 @@
#include "BBLCloudServiceAgent.hpp"
#include "BBLNetworkPlugin.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
BBLCloudServiceAgent::BBLCloudServiceAgent()
{
BOOST_LOG_TRIVIAL(info) << "BBLCloudServiceAgent: Constructor - using BBLNetworkPlugin singleton";
}
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

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__

View file

@ -0,0 +1,823 @@
#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)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": auto-migrating unversioned legacy library to versioned format";
try {
boost::filesystem::rename(legacy_path, versioned_path);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": successfully renamed " << legacy_path.string() << " to "
<< versioned_path.string();
} 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";
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library;
#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;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library;
#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) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": versioned library not found, trying current directory";
std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_NETWORK_LIBRARY));
if (library_path.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", can not get path in current directory for %1%") % BAMBU_NETWORK_LIBRARY;
set_load_error(
"Network library not found",
"Could not locate versioned library: " + library,
library
);
return -1;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", current path %1%")%library_path;
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
);
}
printf("after dlopen, network_module is %p\n", m_networking_module);
#endif
if (!m_networking_module) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", can not Load Library for %1%")%library;
if (!m_load_error.has_error) {
set_load_error(
"Network library failed to load",
"LoadLibrary/dlopen returned null",
library
);
}
return -1;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", successfully loaded library %1%, module %2%")%library %m_networking_module;
// Load file transfer interface
InitFTModule(m_networking_module);
// Load all function pointers
load_all_function_pointers();
if (m_get_version) {
std::string ver = m_get_version();
printf("network plugin version: %s\n", ver.c_str());
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": network plugin version = " << ver;
}
return 0;
}
int BBLNetworkPlugin::unload()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", network module %1%")%m_networking_module;
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) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": agent already exists";
return m_agent;
}
if (m_create_agent) {
m_agent = m_create_agent(log_dir);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", agent=%1%, create_agent=%2%, log_dir=%3%")
% m_agent % (m_create_agent ? "yes" : "no") % log_dir;
return m_agent;
}
int BBLNetworkPlugin::destroy_agent()
{
int ret = 0;
if (m_agent && m_destroy_agent) {
ret = m_destroy_agent(m_agent);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", agent=%1%, destroy_agent=%2%, ret=%3%")
% m_agent % (m_destroy_agent ? "yes" : "no") % ret;
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) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", try load BambuSource directly from current directory");
std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_SOURCE_LIBRARY));
if (library_path.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", can not get path in current directory for %1%") % BAMBU_SOURCE_LIBRARY;
return m_source_module;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", current path %1%")%library_path;
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
if (!function) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find function %1%")%name;
}
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) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", GetModuleFileNameW return error, can not Load Library for %1%") % library_name;
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_LOG_TRIVIAL(info) << __FUNCTION__ << ": removing legacy library at " << legacy_path.string();
boost::system::error_code ec;
boost::filesystem::remove(legacy_path, ec);
if (ec) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to remove legacy library: " << ec.message();
}
}
}
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

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__

View file

@ -0,0 +1,379 @@
#include "BBLPrinterAgent.hpp"
#include "BBLNetworkPlugin.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
BBLPrinterAgent::BBLPrinterAgent()
{
BOOST_LOG_TRIVIAL(info) << "BBLPrinterAgent: Constructor - using BBLNetworkPlugin singleton";
}
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{
.id = "bbl",
.name = "Bambu Lab Printer Agent",
.version = "",
.description = "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

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__

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;
}

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();

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__

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 void fetch_filament_info(std::string dev_id) {}
};
} // namespace Slic3r
#endif // __I_PRINTER_AGENT_HPP__

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,182 @@
#ifndef __MOONRAKER_PRINTER_AGENT_HPP__
#define __MOONRAKER_PRINTER_AGENT_HPP__
#include "IPrinterAgent.hpp"
#include "ICloudServiceAgent.hpp"
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <nlohmann/json.hpp>
namespace Slic3r {
class MoonrakerPrinterAgent final : 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; }
void fetch_filament_info(std::string dev_id) override;
private:
struct PrinthostConfig
{
std::string host;
std::string port;
std::string api_key;
std::string base_url;
std::string model_name;
};
struct MoonrakerDeviceInfo
{
std::string dev_id;
std::string dev_name;
std::string version;
};
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 get_printhost_config(PrinthostConfig& config) const;
bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const;
bool fetch_server_info(const std::string& base_url, const std::string& api_key, std::string& version, std::string& error) const;
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;
std::string resolve_host(const std::string& dev_id) const;
std::string resolve_api_key(const std::string& dev_id, const std::string& fallback) const;
void store_host(const std::string& dev_id, const std::string& host, const std::string& api_key);
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;
// Server info (returns JSON, not just version string)
bool fetch_server_info_json(const std::string& base_url, const std::string& api_key,
nlohmann::json& info, std::string& error) const;
// Connection thread management
void perform_connection_async(const std::string& dev_id,
const std::string& base_url,
const std::string& api_key);
void finish_connection();
mutable std::recursive_mutex state_mutex;
std::map<std::string, std::string> host_by_device;
std::map<std::string, std::string> api_key_by_device;
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;
// Connection thread management
std::atomic<bool> connect_in_progress{false};
std::atomic<bool> connect_stop_requested{false};
std::thread connect_thread;
std::recursive_mutex connect_mutex;
std::condition_variable connect_cv;
};
} // namespace Slic3r
#endif

File diff suppressed because it is too large Load diff

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;
void 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

View file

@ -0,0 +1,176 @@
#include "NetworkAgentFactory.hpp"
#include "IPrinterAgent.hpp"
#include "ICloudServiceAgent.hpp"
#include "BBLPrinterAgent.hpp"
#include "OrcaPrinterAgent.hpp"
#include "QidiPrinterAgent.hpp"
#include "MoonrakerPrinterAgent.hpp"
#include <boost/log/trivial.hpp>
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::string& get_default_agent_id()
{
static std::string default_id;
return default_id;
}
} // 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();
auto result = agents.emplace(id, PrinterAgentInfo(id, display_name, std::move(factory)));
if (result.second) {
BOOST_LOG_TRIVIAL(info) << "Registered printer agent: " << id << " (" << display_name << ")";
// Set as default if it's the first agent registered
auto& default_id = get_default_agent_id();
if (default_id.empty()) {
default_id = id;
}
return true;
} else {
BOOST_LOG_TRIVIAL(warning) << "Printer agent already registered: " << id;
return false;
}
}
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);
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;
}
return it->second.factory(cloud_agent, log_dir);
}
std::string NetworkAgentFactory::get_default_printer_agent_id()
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
return get_default_agent_id();
}
void NetworkAgentFactory::set_default_printer_agent_id(const std::string& id)
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
if (agents.find(id) != agents.end()) {
get_default_agent_id() = id;
BOOST_LOG_TRIVIAL(info) << "Default printer agent set to: " << id;
} else {
BOOST_LOG_TRIVIAL(warning) << "Cannot set default to unregistered agent: " << id;
}
}
void NetworkAgentFactory::register_all_agents()
{
// Register Orca printer agent
{
auto info = OrcaPrinterAgent::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<OrcaPrinterAgent>(log_dir);
if (cloud_agent) {
agent->set_cloud_agent(cloud_agent);
}
return agent;
});
}
// Register Qidi printer agent
{
auto info = QidiPrinterAgent::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<QidiPrinterAgent>(log_dir);
if (cloud_agent) {
agent->set_cloud_agent(cloud_agent);
}
return agent;
});
}
// Register Moonraker printer agent
{
auto info = MoonrakerPrinterAgent::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<MoonrakerPrinterAgent>(log_dir);
if (cloud_agent) {
agent->set_cloud_agent(cloud_agent);
}
return agent;
});
}
// Register BBL printer agent (only if bbl network agent is available)
{
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;
});
}
BOOST_LOG_TRIVIAL(info) << "Registered " << get_printer_agents().size() << " printer agents";
}
} // namespace Slic3r

View file

@ -0,0 +1,250 @@
#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>
#include <map>
#include <mutex>
namespace Slic3r {
// Forward declarations
class ICloudServiceAgent;
class IPrinterAgent;
/**
* AgentProvider - Specifies which implementation to use for each agent type.
*
* - Orca: Native Orca implementations (OrcaCloudServiceAgent, OrcaPrinterAgent)
* - BBL: BBL DLL wrapper implementations (BBLCloudServiceAgent, BBLPrinterAgent)
*/
enum class AgentProvider { Orca, 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)
*
* @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);
/**
* Get default printer agent ID
*/
static std::string get_default_printer_agent_id();
/**
* Set a specific agent as the default
*/
static void set_default_printer_agent_id(const std::string& id);
// ========================================================================
// 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(AgentProvider provider, const std::string& log_dir)
{
switch (provider) {
case AgentProvider::Orca: return std::make_shared<OrcaCloudServiceAgent>(log_dir);
case AgentProvider::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;
}
}
// ========================================================================
// NetworkAgent Facade Creation
// ========================================================================
/**
* Create a NetworkAgent from pre-created sub-agents
*
* @param cloud_agent Cloud service agent (required, includes auth)
* @param printer_agent Printer agent (optional, can be nullptr)
* @return Unique pointer to NetworkAgent facade
*/
static std::unique_ptr<NetworkAgent> create_from_agents(std::shared_ptr<ICloudServiceAgent> cloud_agent,
std::shared_ptr<IPrinterAgent> printer_agent)
{
return std::make_unique<NetworkAgent>(std::move(cloud_agent), std::move(printer_agent));
}
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
*/
inline std::unique_ptr<NetworkAgent> create_agent_from_config(const std::string& log_dir, AppConfig* app_config)
{
// Determine cloud provider from config
bool use_orca_cloud = false;
if (app_config) {
try {
use_orca_cloud = app_config->get("use_orca_cloud") == "true" || app_config->get_bool("use_orca_cloud");
} catch (...) {
use_orca_cloud = false;
}
}
// Create cloud agent
AgentProvider provider = use_orca_cloud ? AgentProvider::Orca : AgentProvider::BBL;
auto cloud_agent = NetworkAgentFactory::create_cloud_agent(provider, log_dir);
// Fall back to Orca if BBL plugin not available
if (!cloud_agent && provider == AgentProvider::BBL) {
BOOST_LOG_TRIVIAL(warning) << "BBL plugin not loaded, falling back to Orca cloud agent";
cloud_agent = NetworkAgentFactory::create_cloud_agent(AgentProvider::Orca, log_dir);
}
if (!cloud_agent) {
BOOST_LOG_TRIVIAL(error) << "Failed to create cloud agent";
return nullptr;
}
// auto bbl_printer_agent = NetworkAgentFactory::create_printer_agent_by_id("bbl", cloud_agent, log_dir);
// Create NetworkAgent with cloud agent only (printer agent added later)
// We will create the printer agent later when the printer is selected, so we pass nullptr for the printer agent here.
auto agent = NetworkAgentFactory::create_from_agents(std::move(cloud_agent), nullptr);
// Configure URL overrides for Orca cloud
if (agent && app_config && use_orca_cloud) {
auto* orca_cloud = dynamic_cast<OrcaCloudServiceAgent*>(agent->get_cloud_agent().get());
if (orca_cloud) {
orca_cloud->configure_urls(app_config);
}
}
BOOST_LOG_TRIVIAL(info) << "Created NetworkAgent with cloud agent";
return agent;
}
} // namespace Slic3r
#endif // __NETWORK_AGENT_FACTORY_HPP__

File diff suppressed because it is too large Load diff

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__

View file

@ -0,0 +1,245 @@
#include "OrcaPrinterAgent.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
const std::string OrcaPrinterAgent_VERSION = "0.0.1";
OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir))
{
BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: Constructor - log_dir=" << this->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;
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: Cloud agent set";
}
// ============================================================================
// Communication - All Stubs
// ============================================================================
int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: send_message (stub) - dev_id=" << dev_id;
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)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: connect_printer (stub) - dev_id=" << dev_id << ", dev_ip=" << dev_ip;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::disconnect_printer()
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: disconnect_printer (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: send_message_to_printer (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS;
}
// ============================================================================
// Certificates - All Stubs
// ============================================================================
int OrcaPrinterAgent::check_cert()
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: check_cert (stub)";
return BAMBU_NETWORK_SUCCESS;
}
void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: install_device_cert (stub) - dev_id=" << dev_id;
}
// ============================================================================
// Discovery - Stub
// ============================================================================
bool OrcaPrinterAgent::start_discovery(bool start, bool sending)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_discovery (stub) - start=" << start << ", sending=" << sending;
return true;
}
// ============================================================================
// Binding - All Stubs
// ============================================================================
int OrcaPrinterAgent::ping_bind(std::string ping_code)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: ping_bind (stub) - ping_code=" << ping_code;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: bind_detect (stub) - dev_ip=" << dev_ip;
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)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: bind (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::unbind(std::string dev_id)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: unbind (stub) - dev_id=" << dev_id;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::request_bind_ticket(std::string* ticket)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: request_bind_ticket (stub)";
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{.id = "orca",
.name = "Orca Printer Agent",
.version = OrcaPrinterAgent_VERSION,
.description = "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)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_print (stub) - task_name=" << params.task_name;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::start_local_print_with_record(PrintParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn,
OnWaitFn wait_fn)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_local_print_with_record (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_send_gcode_to_sdcard (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_local_print (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_sdcard_print (stub)";
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

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__

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;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
#ifndef __QIDI_PRINTER_AGENT_HPP__
#define __QIDI_PRINTER_AGENT_HPP__
#include "OrcaPrinterAgent.hpp"
#include <atomic>
#include <cstdint>
#include <map>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <vector>
namespace Slic3r {
class QidiPrinterAgent final : public OrcaPrinterAgent
{
public:
explicit QidiPrinterAgent(std::string log_dir);
~QidiPrinterAgent() override;
static AgentInfo get_agent_info_static();
AgentInfo get_agent_info() override { return get_agent_info_static(); }
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override;
int send_message_to_printer(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;
bool start_discovery(bool start, bool sending) override;
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override;
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override;
int set_on_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 { return FilamentSyncMode::pull; }
void fetch_filament_info(std::string dev_id) override;
private:
struct PrinthostConfig
{
std::string host;
std::string port;
std::string api_key;
std::string base_url;
std::string model_id;
std::string model_name;
};
struct QidiDeviceInfo
{
std::string dev_id;
std::string dev_name;
std::string model_id;
std::string version;
};
struct QidiSlotInfo
{
int slot_index = 0;
int color_index = 0;
int filament_type = 0;
int vendor_type = 0;
bool filament_exists = false;
};
struct QidiFilamentDict
{
std::map<int, std::string> colors;
std::map<int, std::string> filaments;
};
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 get_printhost_config(PrinthostConfig& config) const;
bool fetch_device_info(const std::string& base_url, const std::string& api_key, QidiDeviceInfo& info, std::string& error) const;
bool fetch_server_info(const std::string& base_url, const std::string& api_key, std::string& version, std::string& error) const;
bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set<std::string>& objects, std::string& error) const;
std::string resolve_host(const std::string& dev_id) const;
std::string resolve_api_key(const std::string& dev_id, const std::string& fallback) const;
void store_host(const std::string& dev_id, const std::string& host, const std::string& api_key);
bool fetch_slot_info(const std::string& base_url,
const std::string& api_key,
std::vector<QidiSlotInfo>& slots,
int& box_count,
std::string& error) const;
bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const;
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 normalize_color(const std::string& color);
static std::string map_filament_type_to_setting_id(const std::string& filament_type);
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 nlohmann::json* ams_override) const;
mutable std::mutex state_mutex;
std::map<std::string, std::string> host_by_device;
std::map<std::string, std::string> api_key_by_device;
std::string ssdp_announced_host;
std::string ssdp_announced_id;
OnMsgArrivedFn on_ssdp_msg_fn;
OnPrinterConnectedFn on_printer_connected_fn;
OnLocalConnectedFn on_local_connect_fn;
OnMessageFn on_message_fn;
OnMessageFn on_local_message_fn;
QueueOnMainFn queue_on_main_fn;
mutable std::mutex payload_mutex;
nlohmann::json status_cache;
nlohmann::json last_ams_payload;
std::atomic<bool> ws_stop{false};
std::atomic<uint64_t> ws_last_emit_ms{0};
std::thread ws_thread;
};
} // namespace Slic3r
#endif

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

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") {