tmp prusa配置文件集

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-24 00:47:32 +08:00
parent 0416922a94
commit 22a6493e24
31 changed files with 2255 additions and 168 deletions

View File

@@ -109,7 +109,7 @@
"Admin": "Admin",
"User": "Benutzer",
"WARNING: Are you sure you want to permanently delete this user AND ALL their uploaded files and G-codes?": "WARNUNG: Sind Sie sicher, dass Sie diesen Benutzer UND ALLE seine Dateien löschen wollen?",
"CuraEngine Configurations": "CuraEngine-Konfigurationen",
"SliceEngine Configurations": "Slicing-Engine-Konfigurationen",
"Plater Origin Offset X (mm)": "Druckbett Ursprung Offset X (mm)",
"Adjust the X-axis compilation offset for combined files on the build plate.": "X-Achsen-Offset für kombinierte Dateien anpassen.",
"Plater Origin Offset Y (mm)": "Druckbett Ursprung Offset Y (mm)",

View File

@@ -103,7 +103,7 @@
"Admin": "Admin",
"User": "User",
"WARNING: Are you sure you want to permanently delete this user AND ALL their uploaded files and G-codes?": "WARNING: Are you sure you want to permanently delete this user AND ALL their uploaded files and G-codes?",
"CuraEngine Configurations": "CuraEngine Configurations",
"SliceEngine Configurations": "SliceEngine Configurations",
"Plater Origin Offset X (mm)": "Plater Origin Offset X (mm)",
"Adjust the X-axis compilation offset for combined files on the build plate.": "Adjust the X-axis compilation offset for combined files on the build plate.",
"Plater Origin Offset Y (mm)": "Plater Origin Offset Y (mm)",

View File

@@ -109,7 +109,7 @@
"Admin": "管理员",
"User": "普通用户",
"WARNING: Are you sure you want to permanently delete this user AND ALL their uploaded files and G-codes?": "警告确定要永久删除该用户以及TA上传的所有文件和切片吗",
"CuraEngine Configurations": "CuraEngine 配置",
"SliceEngine Configurations": "切片引擎配置",
"Plater Origin Offset X (mm)": "构建板原点偏移 X (mm)",
"Adjust the X-axis compilation offset for combined files on the build plate.": "调整多文件在构建板合并切片时的X坐标偏移。",
"Plater Origin Offset Y (mm)": "构建板原点偏移 Y (mm)",

View File

@@ -13,6 +13,7 @@ from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_t
from app import i18n_dict
# import trimesh.repair
from app.utils.stl_simplifier import simplify_stl
from app.utils.slice_engines import get_all_engines
main_bp = Blueprint('main', __name__)
@@ -46,6 +47,8 @@ def settings():
default_support_pattern = request.form.get('default_support_pattern', 'tree')
default_quality = request.form.get('default_quality', 'base_global_standard.inst.cfg')
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()
# update or create config entries
config_items = [
@@ -57,6 +60,8 @@ def settings():
('default_support_pattern', default_support_pattern),
('default_quality', default_quality),
('gcode_upload_folder', gcode_upload_folder),
('slicer_engine', slicer_engine),
('build_plate_model_path', build_plate_model_path),
('default_guest_stl_quota_mb', request.form.get('default_guest_stl_quota_mb', '0')),
('default_guest_gcode_quota_mb', request.form.get('default_guest_gcode_quota_mb', '0')),
('default_user_stl_quota_mb', request.form.get('default_user_stl_quota_mb', '0')),
@@ -75,8 +80,8 @@ def settings():
return redirect(url_for('admin.settings'))
configs = {c.key: c.value for c in SystemConfig.query.all()}
presets = get_quality_presets()
return render_template('admin/settings.html', configs=configs, presets=presets)
engines = get_all_engines()
return render_template('admin/settings.html', configs=configs, engines=engines)
@admin_bp.route('/users')
def users():
@@ -192,18 +197,3 @@ def get_bed_dimensions():
except:
return 200, 200, 200
def get_quality_presets():
try:
path = os.path.join(current_app.root_path, '..', 'print_config', 'quality', 'creality', 'presets')
files = [f for f in os.listdir(path) if f.endswith('.inst.cfg')]
presets = []
for f in files:
# name = f.replace('.inst.cfg', '').replace('base_', '').replace('_', ' ')
name = f.replace('.inst.cfg', '')
presets.append((f, name))
presets.sort(key=lambda x: x[1])
return presets
except:
return []

View File

@@ -338,21 +338,6 @@ def get_bed_dimensions():
except:
return 200, 200, 200
def get_quality_presets():
try:
path = os.path.join(current_app.root_path, '..', 'print_config', 'quality', 'creality', 'presets')
files = [f for f in os.listdir(path) if f.endswith('.inst.cfg')]
presets = []
for f in files:
# name = f.replace('.inst.cfg', '').replace('base_', '').replace('_', ' ')
name = f.replace('.inst.cfg', '')
presets.append((f, name))
presets.sort(key=lambda x: x[1])
return presets
except:
return []
@main_bp.route('/plater')
@login_required
def plater():
@@ -360,7 +345,7 @@ def plater():
quota_exceeded = (quota_mb > 0 and current_size >= quota_mb * 1024 * 1024)
w, h, hd = get_bed_dimensions()
presets = get_quality_presets()
configs = {c.key: c.value for c in SystemConfig.query.all()}
offset_x = float(configs.get('offset_x', '0.0'))
@@ -373,7 +358,7 @@ def plater():
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, presets=presets, 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)
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)
@main_bp.route('/file/<int:file_id>')
@login_required
@@ -553,3 +538,20 @@ def merge_and_slice():
return jsonify({'success': True, 'message': 'Plater slice queued!'})
@main_bp.route('/api/build_plate_model')
@login_required
def build_plate_model():
conf = SystemConfig.query.filter_by(key='build_plate_model_path').first()
if conf and conf.value and os.path.exists(conf.value):
return send_file(conf.value)
abort(404)
@main_bp.route('/api/engine_options/<engine_name>')
@login_required
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})

View File

@@ -7,8 +7,7 @@
<div class="card shadow-sm">
<div class="card-body">
<h5>{{ _('CuraEngine Configurations') }}</h5>
<hr>
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3">{{ _('SliceEngine Configurations') }}</h5>
<form id="settingsForm" onsubmit="submitSettings(event)">
<div class="mb-3">
<label for="offset_x" class="form-label">{{ _('Plater Origin Offset X (mm)') }}</label>
@@ -34,8 +33,13 @@
<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>
<h5 class="mt-4">{{ _('Default Plater Settings') }}</h5>
<hr>
<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>
<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>
<div class="mb-3">
<label for="default_infill" class="form-label">{{ _('Default Infill Density (%)') }}</label>
@@ -53,29 +57,32 @@
<div class="mb-3">
<label for="default_support_pattern" class="form-label">{{ _('Default Support Type') }}</label>
<select class="form-select" name="default_support_pattern" id="default_support_pattern">
<option value="tree" {% if configs.get('default_support_pattern', 'tree') == 'tree' %}selected{% endif %}>{{ _('Tree') }}</option>
<option value="lines" {% if configs.get('default_support_pattern', 'tree') == 'lines' %}selected{% endif %}>{{ _('Lines') }}</option>
<option value="grid" {% if configs.get('default_support_pattern', 'tree') == 'grid' %}selected{% endif %}>{{ _('Grid') }}</option>
<option value="triangles" {% if configs.get('default_support_pattern', 'tree') == 'triangles' %}selected{% endif %}>{{ _('Triangles') }}</option>
<option value="concentric" {% if configs.get('default_support_pattern', 'tree') == 'concentric' %}selected{% endif %}>{{ _('Concentric') }}</option>
<option value="zigzag" {% if configs.get('default_support_pattern', 'tree') == 'zigzag' %}selected{% endif %}>{{ _('Zig Zag') }}</option>
<option value="cross" {% if configs.get('default_support_pattern', 'tree') == 'cross' %}selected{% endif %}>{{ _('Cross') }}</option>
<option value="gyroid" {% if configs.get('default_support_pattern', 'tree') == 'gyroid' %}selected{% endif %}>{{ _('Gyroid') }}</option>
<option value="honeycomb" {% if configs.get('default_support_pattern', 'tree') == 'honeycomb' %}selected{% endif %}>{{ _('Honeycomb') }}</option>
<option value="octagon" {% if configs.get('default_support_pattern', 'tree') == 'octagon' %}selected{% endif %}>{{ _('Octagon') }}</option>
<select class="form-select" name="default_support_pattern" id="default_support_pattern" data-selected="{{ configs.get('default_support_pattern', 'tree') }}">
<!-- Loaded via JS -->
</select>
</div>
<div class="mb-4">
<label for="default_quality" class="form-label">{{ _('Default Quality Profile') }}</label>
<select class="form-select" name="default_quality" id="default_quality">
{% for key, name in presets %}
<option value="{{ key }}" {% if configs.get('default_quality', 'base_global_standard.inst.cfg') == key %}selected{% endif %}>{{ _(name) }}</option>
{% endfor %}
<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>
<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>
<select class="form-select" name="slicer_engine" id="slicer_engine">
{% for engine in engines %}
<option value="{{ engine.name }}" {% if configs.get('slicer_engine', 'cura') == engine.name %}selected{% endif %} {% if not engine.is_available %}disabled{% endif %}>
{{ engine.display_name }} {% if not engine.is_available %}({{ _('Not Available') }}){% endif %}
</option>
{% endfor %}
</select>
<div class="form-text mt-2"><i class="bi bi-info-circle me-1"></i>{{ _('Select the engine to be used globally. Ensure the selected engine is installed and accessible on the server.') }}</div>
</div>
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3"><i class="bi bi-hdd-network me-2"></i>{{ _('Default Storage Quotas (MB)') }}</h5>
<div class="row">
@@ -140,5 +147,42 @@ function submitSettings(event) {
btn.innerHTML = originalText;
});
}
document.addEventListener('DOMContentLoaded', function() {
const engineSelect = document.getElementById('slicer_engine');
const qualitySelect = document.getElementById('default_quality');
const patternSelect = document.getElementById('default_support_pattern');
function updateOptions(engine) {
fetch(`/api/engine_options/${engine}`)
.then(res => res.json())
.then(data => {
const selQ = qualitySelect.getAttribute('data-selected');
qualitySelect.innerHTML = '';
data.presets.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
qualitySelect.appendChild(opt);
});
if(selQ) qualitySelect.value = selQ;
const selP = patternSelect.getAttribute('data-selected');
patternSelect.innerHTML = '';
data.support_patterns.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
patternSelect.appendChild(opt);
});
if(selP) patternSelect.value = selP;
});
}
engineSelect.addEventListener('change', function() {
updateOptions(this.value);
});
// Initial load
updateOptions(engineSelect.value);
});
</script>
{% endblock %}

View File

@@ -95,18 +95,7 @@
</div>
<div class="mb-2">
<label for="support-pattern" class="form-label text-secondary small mb-1">{{ _('Support Type') }}</label>
<select class="form-select form-select-sm" id="support-pattern" {% if default_support == 'false' %}disabled{% endif %}>
<option value="tree" {% if default_support_pattern == 'tree' %}selected{% endif %}>{{ _('Tree') }}</option>
<option value="lines" {% if default_support_pattern == 'lines' %}selected{% endif %}>{{ _('Lines') }}</option>
<option value="grid" {% if default_support_pattern == 'grid' %}selected{% endif %}>{{ _('Grid') }}</option>
<option value="triangles" {% if default_support_pattern == 'triangles' %}selected{% endif %}>{{ _('Triangles') }}</option>
<option value="concentric" {% if default_support_pattern == 'concentric' %}selected{% endif %}>{{ _('Concentric') }}</option>
<option value="zigzag" {% if default_support_pattern == 'zigzag' %}selected{% endif %}>{{ _('Zig Zag') }}</option>
<option value="cross" {% if default_support_pattern == 'cross' %}selected{% endif %}>{{ _('Cross') }}</option>
<option value="gyroid" {% if default_support_pattern == 'gyroid' %}selected{% endif %}>{{ _('Gyroid') }}</option>
<option value="honeycomb" {% if default_support_pattern == 'honeycomb' %}selected{% endif %}>{{ _('Honeycomb') }}</option>
<option value="octagon" {% if default_support_pattern == 'octagon' %}selected{% endif %}>{{ _('Octagon') }}</option>
</select>
<select class="form-select form-select-sm" id="support-pattern" data-selected="{{ default_support_pattern }}" {% if default_support == 'false' %}disabled{% endif %}></select>
</div>
</div>
</div>
@@ -120,11 +109,7 @@
<div id="collapseQuality" class="collapse" data-bs-parent="#platerSidebarAccordion">
<div class="card-body">
<div class="mb-3">
<select class="form-select bg-light" id="quality">
{% for key, name in presets %}
<option value="{{ key }}" {% if key == last_quality %}selected{% endif %}>{{ _(name) }}</option>
{% endfor %}
</select>
<select class="form-select bg-light" id="quality" data-selected="{{ last_quality }}"></select>
</div>
</div>
</div>
@@ -226,6 +211,18 @@ function initPlater() {
axesHelper.position.set(-bedWidth / 2, -bedDepth / 2, 0.2);
scene.add(axesHelper);
{% if configs.get('build_plate_model_path') %}
const bpLoader = new THREE.STLLoader();
bpLoader.load("{{ url_for('main.build_plate_model') }}", function (geometry) {
const material = new THREE.MeshPhongMaterial({ color: 0x999999, specular: 0x111111, shininess: 200 });
const mesh = new THREE.Mesh(geometry, material);
<!-- mesh.rotation.set(-Math.PI / 2, 0, 0); -->
mesh.rotation.set(0, 0, 0);
mesh.position.set(0, 0, -0.1); // Slightly below the grid
scene.add(mesh);
});
{% endif %}
// Show Bed Box outline
const boxGeo = new THREE.BoxGeometry(bedWidth, bedDepth, bedHeight);
const edges = new THREE.EdgesGeometry(boxGeo);
@@ -671,8 +668,17 @@ function addModelToPlate(btnElement, fileId, url, name, status) {
supportSelect.value = data.settings.support;
supportSelect.dispatchEvent(new Event('change'));
}
if (data.settings.support_pattern) document.getElementById('support-pattern').value = data.settings.support_pattern;
if (data.settings.quality) document.getElementById('quality').value = data.settings.quality;
if (data.settings.support_pattern) {
const sSelect = document.getElementById('support-pattern');
sSelect.setAttribute('data-selected', data.settings.support_pattern);
sSelect.value = data.settings.support_pattern;
}
if (data.settings.quality) {
const qSelect = document.getElementById('quality');
qSelect.setAttribute('data-selected', data.settings.quality);
qSelect.value = data.settings.quality;
}
}
} catch (e) {}
}
@@ -948,4 +954,33 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const engine = "{{ configs.get('slicer_engine', 'cura') }}";
const qualitySelect = document.getElementById('quality');
const patternSelect = document.getElementById('support-pattern');
fetch(`/api/engine_options/${engine}`)
.then(res => res.json())
.then(data => {
const selQ = qualitySelect.getAttribute('data-selected');
qualitySelect.innerHTML = '';
data.presets.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
qualitySelect.appendChild(opt);
});
if(selQ) qualitySelect.value = selQ;
const selP = patternSelect.getAttribute('data-selected');
patternSelect.innerHTML = '';
data.support_patterns.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
patternSelect.appendChild(opt);
});
if(selP) patternSelect.value = selP;
});
});
</script>
{% endblock %}

View File

@@ -1,6 +1,13 @@
from .cura_engine import CuraEngine
from .prusa_slicer_engine import PrusaSlicerEngine
def get_all_engines():
"""Returns a list of instantiated engines."""
return [
CuraEngine(),
PrusaSlicerEngine()
]
def get_slicer_engine(engine_name="cura"):
"""
Factory function to retrieve the requested slicing engine instance.
@@ -14,4 +21,4 @@ def get_slicer_engine(engine_name="cura"):
return PrusaSlicerEngine()
else:
# Default fallback
return CuraEngine()
return CuraEngine()

View File

@@ -8,6 +8,26 @@ from app.utils.conf_parse import ConfParse
class CuraEngine:
def __init__(self):
self.name = "cura"
self.display_name = "UltiMaker Cura"
self.is_available = self._check_available()
def _check_available(self):
try:
# check if CuraEngine is available in PATH
result = subprocess.run(["CuraEngine", "help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.returncode == 0 or b"Usage:" in result.stdout or b"Usage:" in result.stderr
except (FileNotFoundError, OSError):
return False
self.display_name = "UltiMaker Cura"
self.is_available = self._check_available()
def _check_available(self):
try:
# check if CuraEngine is available in PATH
result = subprocess.run(["CuraEngine", "help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.returncode == 0 or b"Usage:" in result.stdout or b"Usage:" in result.stderr
except (FileNotFoundError, OSError):
return False
def slice(self, app, stl_filepath, gcode_filepath, **kwargs):
"""
@@ -165,4 +185,31 @@ class CuraEngine:
try:
os.remove(tmp_def_path)
except Exception as e:
app.logger.error(f"Failed to delete temp JSON config {tmp_def_path}: {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')
if not os.path.exists(path): return []
files = [f for f in os.listdir(path) if f.endswith('.inst.cfg')]
presets = []
for f in files:
presets.append({'id': f, 'name': f.replace('.inst.cfg', '')})
presets.sort(key=lambda x: x['name'])
return presets
except:
return []
def get_support_patterns(self):
return [
{'id': 'tree', 'name': 'Tree'},
{'id': 'lines', 'name': 'Lines'},
{'id': 'grid', 'name': 'Grid'},
{'id': 'triangles', 'name': 'Triangles'},
{'id': 'concentric', 'name': 'Concentric'},
{'id': 'zigzag', 'name': 'Zig Zag'},
{'id': 'cross', 'name': 'Cross'},
{'id': 'gyroid', 'name': 'Gyroid'},
{'id': 'honeycomb', 'name': 'Honeycomb'},
{'id': 'octagon', 'name': 'Octagon'}
]

View File

@@ -1,9 +1,19 @@
import os
import subprocess
import configparser
class PrusaSlicerEngine:
def __init__(self):
self.name = "prusa_slicer"
self.display_name = "PrusaSlicer"
self.is_available = self._check_available()
def _check_available(self):
try:
result = subprocess.run(["prusa-slicer", "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return b"Usage:" in result.stdout or b"Slic3r" in result.stdout or b"PrusaSlicer" in result.stdout or result.returncode == 0
except (FileNotFoundError, OSError):
return False
def slice(self, app, stl_filepath, gcode_filepath, **kwargs):
"""
@@ -55,3 +65,25 @@ class PrusaSlicerEngine:
except Exception as e:
app.logger.error(f"PrusaSlicer Exception: {e}")
return False, str(e)
def get_quality_presets(self, app):
all_files = [f for f in os.listdir(os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer',"quality")) if f.endswith('.ini')]
quality_presets = []
for file in all_files:
with open(os.path.join(app.root_path, '..', 'print_config', 'prusa_slicer', "quality", file), 'r') as f:
config = configparser.ConfigParser()
config.read_file(f)
if 'metadata' in config:
quality_presets.append({
'id': file.replace('.ini', ''),
'name': config['metadata'].get('show_name', file.replace('.ini', '').replace('_', ' '))
})
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'}
]