能用prusa切片和预览了,添加了缺失的翻译

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-24 01:51:08 +08:00
parent 22a6493e24
commit 366372da6e
15 changed files with 394 additions and 38 deletions

View File

@@ -218,5 +218,12 @@
"Add User": "Benutzer hinzufügen",
"Password": "Passwort",
"Is Admin": "Ist Administrator",
"Create User": "Benutzer erstellen"
"Create User": "Benutzer erstellen",
"Build Plate Model Path (.stl)": "Pfad zum Build-Platte-Modell (.stl)",
"Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.": "Absoluter Pfad zum benutzerdefinierten Build-Platte-STL-Modell, das im Plater angezeigt werden soll. Leer lassen, um keines zu verwenden.",
"Default Material Profile": "Standard Materialprofil",
"Slicing Engine Configurations": "Slicer-Engine-Konfigurationen",
"Slicing Engine": "Slicer-Engine",
"Select the engine to be used globally. Ensure the selected engine is installed and accessible on the server.": "Wählen Sie die Engine aus, die global verwendet werden soll. Stellen Sie sicher, dass die ausgewählte Engine installiert und auf dem Server zugänglich ist.",
"Material Profile": "Materialprofil"
}

View File

@@ -218,5 +218,12 @@
"Add User": "Add User",
"Password": "Password",
"Is Admin": "Is Admin",
"Create User": "Create User"
"Create User": "Create User",
"Build Plate Model Path (.stl)": "Build Plate Model Path (.stl)",
"Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.": "Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.",
"Default Material Profile": "Default Material Profile",
"Slicing Engine Configurations": "Slicing Engine Configurations",
"Slicing Engine": "Slicing Engine",
"Select the engine to be used globally. Ensure the selected engine is installed and accessible on the server.": "Select the engine to be used globally. Ensure the selected engine is installed and accessible on the server.",
"Material Profile": "Material Profile"
}

View File

@@ -218,5 +218,12 @@
"Add User": "添加用户",
"Password": "密码",
"Is Admin": "设为管理员",
"Create User": "创建用户"
"Create User": "创建用户",
"Build Plate Model Path (.stl)": "构建板模型路径 (.stl)",
"Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.": "保存自定义构建板 STL 模型的绝对路径,以在 plater 中显示。留空以使用默认值。",
"Default Material Profile": "默认材料配置",
"Slicing Engine Configurations": "切片引擎配置",
"Slicing Engine": "切片引擎",
"Select the engine to be used globally. Ensure the selected engine is installed and accessible on the server.": "选择要全局使用的引擎。确保所选引擎已安装且在服务器上可访问。",
"Material Profile": "材料配置"
}

View File

@@ -46,6 +46,7 @@ def settings():
default_support = request.form.get('default_support', 'false')
default_support_pattern = request.form.get('default_support_pattern', 'tree')
default_quality = request.form.get('default_quality', 'base_global_standard.inst.cfg')
default_material = request.form.get('default_material', '')
gcode_upload_folder = request.form.get('gcode_upload_folder', '').strip()
slicer_engine = request.form.get('slicer_engine', 'cura')
build_plate_model_path = request.form.get('build_plate_model_path', '').strip()
@@ -59,6 +60,7 @@ def settings():
('default_support', default_support),
('default_support_pattern', default_support_pattern),
('default_quality', default_quality),
('default_material', default_material),
('gcode_upload_folder', gcode_upload_folder),
('slicer_engine', slicer_engine),
('build_plate_model_path', build_plate_model_path),

View File

@@ -355,10 +355,11 @@ def plater():
default_support = configs.get('default_support', 'false')
default_support_pattern = configs.get('default_support_pattern', 'tree')
default_quality = configs.get('default_quality', 'base_global_standard.inst.cfg')
default_material = configs.get('default_material', '')
user_files = PrintFile.query.filter_by(user_id=current_user.id, file_type='stl').order_by(PrintFile.created_at.desc()).all()
models = [{'id': f.id, 'name': f.original_filename, 'status': f.status, 'url': url_for('main.serve_proxy_file', file_id=f.id), 'transform_matrix': f.transform_matrix} for f in user_files]
return render_template('slice/plater.html', w=w, h=h, hd=hd, last_quality=default_quality, models=models, offset_x=offset_x, offset_y=offset_y, default_infill=default_infill, default_support=default_support, default_support_pattern=default_support_pattern, quota_exceeded=quota_exceeded, configs=configs)
return render_template('slice/plater.html', w=w, h=h, hd=hd, last_quality=default_quality, last_material=default_material, models=models, offset_x=offset_x, offset_y=offset_y, default_infill=default_infill, default_support=default_support, default_support_pattern=default_support_pattern, quota_exceeded=quota_exceeded, configs=configs)
@main_bp.route('/file/<int:file_id>')
@login_required
@@ -390,6 +391,7 @@ def merge_and_slice():
data = request.json
pieces = data.get('pieces', [])
quality = data.get('quality', 'base_global_standard.inst.cfg')
material = data.get('material', '')
infill_density = data.get('infill', '20')
support_enable = data.get('support', 'false')
support_pattern = data.get('support_pattern', 'lines')
@@ -427,6 +429,7 @@ def merge_and_slice():
"matrix": p['raw_matrix'],
"settings": {
"quality": quality,
"material": material,
"infill": infill_density,
"support": support_enable,
"support_pattern": support_pattern
@@ -450,6 +453,7 @@ def merge_and_slice():
"parts": [],
"settings": {
"quality": quality,
"material": material,
"infill": infill_density,
"support": support_enable,
"support_pattern": support_pattern
@@ -471,6 +475,7 @@ def merge_and_slice():
"matrix": pieces[0].get('raw_matrix', pieces[0]['matrix']),
"settings": {
"quality": quality,
"material": material,
"infill": infill_density,
"support": support_enable,
"support_pattern": support_pattern
@@ -482,7 +487,7 @@ def merge_and_slice():
temp_filename = f"temp_edit_{uuid.uuid4().hex}.stl"
temp_filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], temp_filename)
merge_and_slice_task(target_file_id, inputs, temp_filepath, quality, infill_density, support_enable, support_pattern, delete_stl=True)
merge_and_slice_task(target_file_id, inputs, temp_filepath, quality, material, infill_density, support_enable, support_pattern, delete_stl=True)
elif len(inputs) == 1 and is_edit:
target_file_id = pieces[0]['file_id']
print_file = PrintFile.query.get(target_file_id)
@@ -495,7 +500,7 @@ def merge_and_slice():
temp_filename = f"temp_{uuid.uuid4().hex}.stl"
temp_filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], temp_filename)
merge_and_slice_task(target_file_id, inputs, temp_filepath, quality, infill_density, support_enable, support_pattern, delete_stl=True)
merge_and_slice_task(target_file_id, inputs, temp_filepath, quality, material, infill_density, support_enable, support_pattern, delete_stl=True)
else:
# Multiple models, create a new "Merged Slice" PrintFile entry to keep track of combination
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
@@ -508,6 +513,7 @@ def merge_and_slice():
"parts": [],
"settings": {
"quality": quality,
"material": material,
"infill": infill_density,
"support": support_enable,
"support_pattern": support_pattern
@@ -534,7 +540,7 @@ def merge_and_slice():
db.session.add(print_file)
db.session.commit()
merge_and_slice_task(print_file.id, inputs, merged_filepath, quality, infill_density, support_enable, support_pattern, delete_stl=False)
merge_and_slice_task(print_file.id, inputs, merged_filepath, quality, material, infill_density, support_enable, support_pattern, delete_stl=False)
return jsonify({'success': True, 'message': 'Plater slice queued!'})
@@ -553,5 +559,6 @@ def engine_options(engine_name):
from app.utils.slice_engines import get_slicer_engine
engine = get_slicer_engine(engine_name)
presets = engine.get_quality_presets(current_app)
patterns = engine.get_support_patterns()
return jsonify({'presets': presets, 'support_patterns': patterns})
patterns = engine.get_support_patterns(current_app)
materials = engine.get_materials(current_app) if hasattr(engine, 'get_materials') else []
return jsonify({'presets': presets, 'support_patterns': patterns, 'materials': materials})

View File

@@ -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 => {

View File

@@ -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>

View File

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

View File

@@ -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 => {

View File

@@ -68,12 +68,17 @@ class CuraEngine:
preset_path = os.path.join(presets_path, 'creality', 'presets', quality_preset)
if os.path.exists(preset_path):
config.read(preset_path)
material_type = config.get('metadata', 'material', fallback=None)
material_type_from_preset = config.get('metadata', 'material', fallback=None)
variant_type = config.get('metadata', 'variant', fallback=None)
quality_type = config.get('metadata', 'quality_type', fallback=None)
# Use explicit material if provided, otherwise fallback to preset's material
material_type = kwargs.get('material_preset') or material_type_from_preset
if material_type:
m_path = os.path.join(materials_path, f"{material_type}.inst.cfg")
if not os.path.exists(m_path) and kwargs.get('material_preset'):
m_path = os.path.join(materials_path, f"{kwargs.get('material_preset')}")
if os.path.exists(m_path): inst_files_list.append(m_path)
if variant_type:
variant_d = variant_type.split("mm")[0]
@@ -186,7 +191,7 @@ class CuraEngine:
os.remove(tmp_def_path)
except Exception as e:
app.logger.error(f"Failed to delete temp JSON config {tmp_def_path}: {e}")
def get_quality_presets(self, app):
try:
path = os.path.join(app.root_path, '..', 'print_config', 'cura_engine', 'quality', 'creality', 'presets')
@@ -200,7 +205,7 @@ class CuraEngine:
except:
return []
def get_support_patterns(self):
def get_support_patterns(self, app):
return [
{'id': 'tree', 'name': 'Tree'},
{'id': 'lines', 'name': 'Lines'},
@@ -213,3 +218,16 @@ class CuraEngine:
{'id': 'honeycomb', 'name': 'Honeycomb'},
{'id': 'octagon', 'name': 'Octagon'}
]
def get_materials(self, app):
try:
path = os.path.join(app.root_path, '..', 'print_config', 'cura_engine', 'materials')
if not os.path.exists(path): return []
files = [f for f in os.listdir(path) if f.endswith('.inst.cfg')]
materials = []
for f in files:
materials.append({'id': f, 'name': f.replace('.inst.cfg', '').replace('generic_', 'Generic ').replace('_', ' ').title()})
materials.sort(key=lambda x: x['name'])
return materials
except:
return []

View File

@@ -30,10 +30,21 @@ class PrusaSlicerEngine:
# Map quality, infill, supports to PrusaSlicer CLI arguments.
# Example defaults, normally these would load from an .ini or be dynamically matched.
quality_preset = kwargs.get('quality_preset')
material_preset = kwargs.get('material_preset')
infill_density = kwargs.get('infill_density')
support_enable = kwargs.get('support_enable')
support_pattern = kwargs.get('support_pattern')
if quality_preset:
q_ini = os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer', 'quality', f"{quality_preset}.ini")
if os.path.exists(q_ini):
command.extend(['--load', q_ini])
if material_preset:
m_ini = os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer', 'materials', f"{material_preset}.ini")
if os.path.exists(m_ini):
command.extend(['--load', m_ini])
if infill_density is not None:
command.extend(["--fill-density", f"{infill_density}%"])
@@ -80,10 +91,29 @@ class PrusaSlicerEngine:
})
return quality_presets
def get_support_patterns(self):
return [
{'id': 'rectilinear', 'name': 'Rectilinear'},
{'id': 'grid', 'name': 'Grid'},
{'id': 'organic', 'name': 'Organic (Tree)'},
{'id': 'snug', 'name': 'Snug'}
]
def get_support_patterns(self, app):
all_files = [f for f in os.listdir(os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer',"supports")) if f.endswith('.ini')]
support_presets = []
for file in all_files:
with open(os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer', "supports", file), 'r') as f:
config = configparser.ConfigParser()
config.read_file(f)
if 'metadata' in config:
support_presets.append({
'id': file.replace('.ini', ''),
'name': config['metadata'].get('show_name', file.replace('.ini', '').replace('_', ' '))
})
return support_presets
def get_materials(self, app):
try:
path = os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer', 'materials')
if not os.path.exists(path): return []
files = [f for f in os.listdir(path) if f.endswith('.ini')]
materials = []
for f in files:
materials.append({'id': f.replace('.ini', ''), 'name': f.replace('.ini', '').replace('_', ' ')})
materials.sort(key=lambda x: x['name'])
return materials
except:
return []

View File

@@ -26,7 +26,7 @@ def get_gcode_dir(app):
@huey.task()
def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
def slice_stl_task(file_id, stl_filepath, quality_preset=None, material_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
# This is run by the Huey worker
# We need to create an app context to interact with the database
from app import create_app
@@ -61,6 +61,7 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
stl_filepath=stl_filepath,
gcode_filepath=gcode_filepath,
quality_preset=quality_preset,
material_preset=material_preset,
infill_density=infill_density,
support_enable=support_enable,
support_pattern=support_pattern
@@ -95,7 +96,7 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
@huey.task()
def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None, material_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
from app import create_app
app = create_app()
with app.app_context():
@@ -112,7 +113,7 @@ def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None,
# Now trigger the regular slicing task
# We can just call the slicing logic or enqueue it
slice_stl_task(file_id, merged_filepath, quality_preset, infill_density, support_enable, support_pattern, delete_stl=delete_stl)
slice_stl_task(file_id, merged_filepath, quality_preset, material_preset, infill_density, support_enable, support_pattern, delete_stl=delete_stl)
except Exception as e:
print_file = PrintFile.query.get(file_id)
if print_file:

View File

@@ -0,0 +1,50 @@
[metadata]
show_name = Grid Support
[settings]
enable_support = 1
support_filament = 0
support_line_width = 0.4
support_interface_filament = 0
support_on_build_plate_only = 0
support_top_z_distance = 0.2
support_interface_loop_pattern = 0
support_interface_top_layers = 2
support_interface_spacing = 0.5
support_interface_speed = 100%
support_base_pattern = rectilinear
support_base_pattern_spacing = 2.5
support_speed = 50
support_threshold_angle = 30
support_object_xy_distance = 0.35
support_type = normal(auto)
support_style = Grid
support_interface_bottom_layers = 2
tree_support_branch_angle = 45
tree_support_wall_count = 0
support_angle = 0
support_bottom_interface_spacing = 0.5
support_bottom_z_distance = 0.2
support_critical_regions_only = 0
support_expansion = 0
support_interface_not_for_body = 1
support_interface_pattern = auto
support_remove_small_overhang = 1
support_xy_overrides_z = xy_overrides_z
tree_support_adaptive_layer_height = 1
tree_support_angle_slow = 25
tree_support_auto_brim = 1
tree_support_branch_angle_organic = 40
tree_support_branch_diameter = 2
tree_support_branch_diameter_angle = 5
tree_support_branch_diameter_double_wall = 3
tree_support_branch_diameter_organic = 2
tree_support_branch_distance = 5
tree_support_branch_distance_organic = 1
tree_support_brim_width = 3
tree_support_tip_diameter = 0.8
tree_support_top_rate = 30%

View File

@@ -0,0 +1,50 @@
[metadata]
show_name = Snug Support
[settings]
enable_support = 1
support_filament = 0
support_line_width = 0.4
support_interface_filament = 0
support_on_build_plate_only = 0
support_top_z_distance = 0.2
support_interface_loop_pattern = 0
support_interface_top_layers = 2
support_interface_spacing = 0.5
support_interface_speed = 100%
support_base_pattern = rectilinear
support_base_pattern_spacing = 2.5
support_speed = 50
support_threshold_angle = 30
support_object_xy_distance = 0.35
support_type = normal(auto)
support_style = Snug
support_interface_bottom_layers = 2
tree_support_branch_angle = 45
tree_support_wall_count = 0
support_angle = 0
support_bottom_interface_spacing = 0.5
support_bottom_z_distance = 0.2
support_critical_regions_only = 0
support_expansion = 0
support_interface_not_for_body = 1
support_interface_pattern = auto
support_remove_small_overhang = 1
support_xy_overrides_z = xy_overrides_z
tree_support_adaptive_layer_height = 1
tree_support_angle_slow = 25
tree_support_auto_brim = 1
tree_support_branch_angle_organic = 40
tree_support_branch_diameter = 2
tree_support_branch_diameter_angle = 5
tree_support_branch_diameter_double_wall = 3
tree_support_branch_diameter_organic = 2
tree_support_branch_distance = 5
tree_support_branch_distance_organic = 1
tree_support_brim_width = 3
tree_support_tip_diameter = 0.8
tree_support_top_rate = 30%

View File

@@ -0,0 +1,50 @@
[metadata]
show_name = Tree Support
[settings]
enable_support = 1
support_filament = 0
support_line_width = 0.4
support_interface_filament = 0
support_on_build_plate_only = 0
support_top_z_distance = 0.2
support_interface_loop_pattern = 0
support_interface_top_layers = 2
support_interface_spacing = 0.5
support_interface_speed = 100%
support_base_pattern = rectilinear
support_base_pattern_spacing = 2.5
support_speed = 50
support_threshold_angle = 30
support_object_xy_distance = 0.35
support_type = normal(auto)
support_style = Organic
support_interface_bottom_layers = 2
tree_support_branch_angle = 45
tree_support_wall_count = 0
support_angle = 0
support_bottom_interface_spacing = 0.5
support_bottom_z_distance = 0.2
support_critical_regions_only = 0
support_expansion = 0
support_interface_not_for_body = 1
support_interface_pattern = auto
support_remove_small_overhang = 1
support_xy_overrides_z = xy_overrides_z
tree_support_adaptive_layer_height = 1
tree_support_angle_slow = 25
tree_support_auto_brim = 1
tree_support_branch_angle_organic = 40
tree_support_branch_diameter = 2
tree_support_branch_diameter_angle = 5
tree_support_branch_diameter_double_wall = 3
tree_support_branch_diameter_organic = 2
tree_support_branch_distance = 5
tree_support_branch_distance_organic = 1
tree_support_brim_width = 3
tree_support_tip_diameter = 0.8
tree_support_top_rate = 30%