@@ -12,25 +12,25 @@
|
||||
<div class="mb-3">
|
||||
<label for="offset_x" class="form-label">{{ _('Plater Origin Offset X (mm)') }}</label>
|
||||
<input type="number" class="form-control" name="offset_x" id="offset_x" value="{{ configs.get('offset_x', '0') }}">
|
||||
<div class="form-text">{{ _('Adjust the X-axis compilation offset for combined files on the build plate.') }}</div>
|
||||
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Adjust the X-axis compilation offset for combined files on the build plate.') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="offset_y" class="form-label">{{ _('Plater Origin Offset Y (mm)') }}</label>
|
||||
<input type="number" class="form-control" name="offset_y" id="offset_y" value="{{ configs.get('offset_y', '0') }}">
|
||||
<div class="form-text">{{ _('Adjust the Y-axis compilation offset for combined files on the build plate.') }}</div>
|
||||
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Adjust the Y-axis compilation offset for combined files on the build plate.') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="proxy_skip_size_mb" class="form-label">{{ _('Proxy Skip Size (MB)') }}</label>
|
||||
<input type="number" class="form-control" name="proxy_skip_size_mb" id="proxy_skip_size_mb" value="{{ configs.get('proxy_skip_size_mb', '5.0') }}" step="0.1" min="0">
|
||||
<div class="form-text">{{ _('Files smaller than this will not generate a simplified proxy.') }}</div>
|
||||
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Files smaller than this will not generate a simplified proxy.') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="gcode_upload_folder" class="form-label"><i class="bi bi-folder2-open me-2"></i>{{ _('Custom GCode Output Folder') }}</label>
|
||||
<input type="text" class="form-control" name="gcode_upload_folder" id="gcode_upload_folder" value="{{ configs.get('gcode_upload_folder', '') }}">
|
||||
<div class="form-text">{{ _('Absolute path to save locally sliced GCode files (e.g. OctoPrint uploads folder like "/home/pi/.octoprint/uploads"). Leave empty to use system default.') }}</div>
|
||||
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Absolute path to save locally sliced GCode files (e.g. OctoPrint uploads folder like "/home/pi/.octoprint/uploads"). Leave empty to use system default.') }}</div>
|
||||
</div>
|
||||
|
||||
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3"><i class="bi bi-grid-3x3 me-2"></i>{{ _('Default Plater Settings') }}</h5>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="build_plate_model_path" class="form-label">{{ _('Build Plate Model Path (.stl)') }}</label>
|
||||
<input type="text" class="form-control" name="build_plate_model_path" id="build_plate_model_path" value="{{ configs.get('build_plate_model_path', '') }}">
|
||||
<div class="form-text">{{ _('Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.') }}</div>
|
||||
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -62,13 +62,20 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="mb-3">
|
||||
<label for="default_quality" class="form-label">{{ _('Default Quality Profile') }}</label>
|
||||
<select class="form-select" name="default_quality" id="default_quality" data-selected="{{ configs.get('default_quality', 'base_global_standard.inst.cfg') }}">
|
||||
<!-- Loaded via JS -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="default_material" class="form-label">{{ _('Default Material Profile') }}</label>
|
||||
<select class="form-select" name="default_material" id="default_material" data-selected="{{ configs.get('default_material', '') }}">
|
||||
<!-- Loaded via JS -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3"><i class="bi bi-cpu me-2"></i>{{ _('Slicing Engine Configurations') }}</h5>
|
||||
<div class="mb-3">
|
||||
<label for="slicer_engine" class="form-label">{{ _('Slicing Engine') }}</label>
|
||||
@@ -151,6 +158,7 @@ function submitSettings(event) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const engineSelect = document.getElementById('slicer_engine');
|
||||
const qualitySelect = document.getElementById('default_quality');
|
||||
const materialSelect = document.getElementById('default_material');
|
||||
const patternSelect = document.getElementById('default_support_pattern');
|
||||
|
||||
function updateOptions(engine) {
|
||||
@@ -166,6 +174,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
if(selQ) qualitySelect.value = selQ;
|
||||
|
||||
const selM = materialSelect.getAttribute('data-selected');
|
||||
materialSelect.innerHTML = '';
|
||||
// Add an empty option for material (optional fallback)
|
||||
const emptyOpt = document.createElement('option');
|
||||
emptyOpt.value = ''; emptyOpt.textContent = "{{ _('Auto / Default') }}";
|
||||
materialSelect.appendChild(emptyOpt);
|
||||
if(data.materials) {
|
||||
data.materials.forEach(p => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id; opt.textContent = p.name;
|
||||
materialSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
if(selM) materialSelect.value = selM;
|
||||
|
||||
const selP = patternSelect.getAttribute('data-selected');
|
||||
patternSelect.innerHTML = '';
|
||||
data.support_patterns.forEach(p => {
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="langDropdown">
|
||||
<li><a class="dropdown-item {% if request.cookies.get('lang') == 'en' %}active{% endif %}" href="{{ url_for('main.set_language', lang='en') }}"><i class="bi bi-translate me-2"></i>English</a></li>
|
||||
<li><a class="dropdown-item {% if request.cookies.get('lang') == 'zh-cn' %}active{% endif %}" href="{{ url_for('main.set_language', lang='zh-cn') }}"><i class="bi bi-translate me-2"></i>中文 (简体)</a></li>
|
||||
<li><a class="dropdown-item {% if request.cookies.get('lang') == 'de' %}active{% endif %}" href="{{ url_for('main.set_language', lang='de') }}"><i class="bi bi-translate me-2"></i>Deutsch</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -66,8 +66,25 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
'SKIRT': new THREE.Color(0x00ffff),
|
||||
'SUPPORT-INTERFACE': new THREE.Color(0x2b6b2b),
|
||||
'TRAVEL': new THREE.Color(0x405060),
|
||||
'DEFAULT': new THREE.Color(0xaaaaaa),
|
||||
'DEFAULT': new THREE.Color(0xaaaaaa)
|
||||
};
|
||||
|
||||
// Additional aliases mapped to basic COLORS
|
||||
const COLOR_ALIASES = {
|
||||
'External perimeter': COLORS['WALL-OUTER'],
|
||||
'Overhang perimeter': COLORS['WALL-OUTER'],
|
||||
'Perimeter': COLORS['WALL-INNER'],
|
||||
'Internal infill': COLORS['FILL'],
|
||||
'Solid infill': COLORS['FILL'],
|
||||
'Top solid infill': COLORS['FILL'],
|
||||
'Bridge infill': COLORS['FILL'],
|
||||
'Support material': COLORS['SUPPORT'],
|
||||
'Skirt/Brim': COLORS['SKIRT'],
|
||||
'Support material interface': COLORS['SUPPORT-INTERFACE']
|
||||
};
|
||||
|
||||
// Merge aliases into COLORS
|
||||
Object.assign(COLORS, COLOR_ALIASES);
|
||||
|
||||
// Inject printer machine dimensions via Jinja
|
||||
const bedWidth = {{ machine_width | default(220) }};
|
||||
@@ -83,6 +100,23 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
'SKIRT': 7, 'SUPPORT-INTERFACE': 8
|
||||
};
|
||||
|
||||
// Aliases for TYPE_INDEX
|
||||
const TYPE_INDEX_ALIASES = {
|
||||
'External perimeter': 1,
|
||||
'Overhang perimeter': 1,
|
||||
'Perimeter': 2,
|
||||
'Internal infill': 3,
|
||||
'Solid infill': 3,
|
||||
'Top solid infill': 3,
|
||||
'Bridge infill': 3,
|
||||
'Support material': 5,
|
||||
'Skirt/Brim': 7,
|
||||
'Support material interface': 8
|
||||
};
|
||||
|
||||
// Merge aliases into TYPE_INDEX
|
||||
Object.assign(TYPE_INDEX, TYPE_INDEX_ALIASES);
|
||||
|
||||
let layers = [];
|
||||
let scene, camera, renderer, controls;
|
||||
let group = new THREE.Group();
|
||||
@@ -159,6 +193,21 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
'SUPPORT-INTERFACE': 'uShowSupportInterface',
|
||||
'TRAVEL': 'uShowTravel'
|
||||
};
|
||||
|
||||
const uniformMapAliases = {
|
||||
'External perimeter': 'uShowOuter',
|
||||
'Overhang perimeter': 'uShowOuter',
|
||||
'Perimeter': 'uShowInner',
|
||||
'Internal infill': 'uShowInfill',
|
||||
'Solid infill': 'uShowInfill',
|
||||
'Top solid infill': 'uShowInfill',
|
||||
'Bridge infill': 'uShowInfill',
|
||||
'Support material': 'uShowSupport',
|
||||
'Skirt/Brim': 'uShowSkirt',
|
||||
'Support material interface': 'uShowSupportInterface'
|
||||
};
|
||||
|
||||
Object.assign(uniformMap, uniformMapAliases);
|
||||
|
||||
document.querySelectorAll('.legend-item').forEach(el => {
|
||||
el.addEventListener('click', function() {
|
||||
@@ -292,16 +341,29 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let chunk = lines[i].trim().toUpperCase();
|
||||
let chunk = lines[i].trim();
|
||||
if (!chunk) continue;
|
||||
let upperChunk = chunk.toUpperCase();
|
||||
|
||||
if (chunk.startsWith(';LAYER:')) {
|
||||
if (upperChunk.startsWith(';LAYER:')) {
|
||||
flushLayer();
|
||||
} else if (chunk.startsWith(';TYPE:')) {
|
||||
} else if (upperChunk.startsWith(';TYPE:')) {
|
||||
currentTypeStr = chunk.substring(6).trim();
|
||||
} else if (chunk.startsWith('G0') || chunk.startsWith('G1')) {
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes(' perimeter')) {
|
||||
// Heuristics for Prusa/Slic3r specific comments like `; External perimeter`
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes(' infill')) {
|
||||
// Heuristics for Prusa/Slic3r specific comments like `; Internal infill`
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes(' material')) {
|
||||
// Support material
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes('Skirt/Brim')) {
|
||||
// Skirt/Brim
|
||||
currentTypeStr = 'Skirt/Brim';
|
||||
} else if (upperChunk.startsWith('G0') || upperChunk.startsWith('G1')) {
|
||||
let next = { x: current.x, y: current.y, z: current.z, e: current.e };
|
||||
let parts = chunk.split(/\s+/);
|
||||
let parts = upperChunk.split(/\s+/);
|
||||
let hasMove = false;
|
||||
|
||||
for (let p of parts) {
|
||||
@@ -314,11 +376,17 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
if (hasMove && !isNaN(next.x) && !isNaN(next.y) && !isNaN(next.z)) {
|
||||
let isExtrude = (next.e > current.e);
|
||||
// Cura uses G0 for travel generally
|
||||
if (chunk.startsWith('G0') && !chunk.includes('E')) isExtrude = false;
|
||||
if (upperChunk.startsWith('G0') && !upperChunk.includes('E')) isExtrude = false;
|
||||
|
||||
let activeType = isExtrude ? currentTypeStr : 'TRAVEL';
|
||||
let col = COLORS[activeType] || COLORS['DEFAULT'];
|
||||
let tIdx = TYPE_INDEX[activeType] !== undefined ? TYPE_INDEX[activeType] : TYPE_INDEX['DEFAULT'];
|
||||
// Special case for default aliases like "; External perimeter" which we stored in currentTypeStr
|
||||
let resolvedType = activeType;
|
||||
if (isExtrude && Object.keys(COLOR_ALIASES).includes(activeType)) {
|
||||
resolvedType = activeType;
|
||||
}
|
||||
|
||||
let col = COLORS[resolvedType] || COLORS['DEFAULT'];
|
||||
let tIdx = TYPE_INDEX[resolvedType] !== undefined ? TYPE_INDEX[resolvedType] : TYPE_INDEX['DEFAULT'];
|
||||
|
||||
if (isExtrude) {
|
||||
let dx = next.x - current.x;
|
||||
|
||||
@@ -101,6 +101,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm flex-shrink-0 mb-3">
|
||||
<div class="card-header bg-light fw-bold text-secondary d-flex justify-content-between align-items-center collapsed" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapseMaterial" aria-expanded="false">
|
||||
<span><i class="bi bi-box me-2"></i>{{ _('Material Profile') }}</span>
|
||||
<i class="bi bi-chevron-bar-contract"></i>
|
||||
</div>
|
||||
<div id="collapseMaterial" class="collapse" data-bs-parent="#platerSidebarAccordion">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<select class="form-select bg-light" id="material" data-selected="{{ last_material }}"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm flex-shrink-0 mb-3">
|
||||
<div class="card-header bg-light fw-bold text-secondary d-flex justify-content-between align-items-center collapsed" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapseQuality" aria-expanded="false">
|
||||
<span><i class="bi bi-gear-wide-connected me-2"></i>{{ _('Quality Profile') }}</span>
|
||||
@@ -679,6 +693,11 @@ function addModelToPlate(btnElement, fileId, url, name, status) {
|
||||
qSelect.setAttribute('data-selected', data.settings.quality);
|
||||
qSelect.value = data.settings.quality;
|
||||
}
|
||||
if (data.settings.material) {
|
||||
const mSelect = document.getElementById('material');
|
||||
mSelect.setAttribute('data-selected', data.settings.material);
|
||||
mSelect.value = data.settings.material;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -882,6 +901,7 @@ function mergeAndSlice() {
|
||||
});
|
||||
|
||||
const quality = document.getElementById('quality').value;
|
||||
const material = document.getElementById('material').value;
|
||||
const infill = document.getElementById('infill-density').value;
|
||||
const support = document.getElementById('support-type').value;
|
||||
const supportPattern = document.getElementById('support-pattern').value;
|
||||
@@ -900,7 +920,7 @@ function mergeAndSlice() {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ pieces: pieces, quality: quality, infill: infill, support: support, support_pattern: supportPattern, is_edit: isEdit, target_file_id: targetFileId })
|
||||
body: JSON.stringify({ pieces: pieces, quality: quality, material: material, infill: infill, support: support, support_pattern: supportPattern, is_edit: isEdit, target_file_id: targetFileId })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
@@ -959,6 +979,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const engine = "{{ configs.get('slicer_engine', 'cura') }}";
|
||||
const qualitySelect = document.getElementById('quality');
|
||||
const patternSelect = document.getElementById('support-pattern');
|
||||
const materialSelect = document.getElementById('material');
|
||||
|
||||
fetch(`/api/engine_options/${engine}`)
|
||||
.then(res => res.json())
|
||||
@@ -972,6 +993,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
if(selQ) qualitySelect.value = selQ;
|
||||
|
||||
const selM = materialSelect.getAttribute('data-selected');
|
||||
materialSelect.innerHTML = '';
|
||||
const emptyOpt = document.createElement('option');
|
||||
emptyOpt.value = ''; emptyOpt.textContent = "{{ _('Auto / Default') }}";
|
||||
materialSelect.appendChild(emptyOpt);
|
||||
if(data.materials) {
|
||||
data.materials.forEach(p => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id; opt.textContent = p.name;
|
||||
materialSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
if(selM) materialSelect.value = selM;
|
||||
|
||||
const selP = patternSelect.getAttribute('data-selected');
|
||||
patternSelect.innerHTML = '';
|
||||
data.support_patterns.forEach(p => {
|
||||
|
||||
Reference in New Issue
Block a user