分离cura,加入prusa,未测试
This commit is contained in:
17
app/utils/slice_engines/__init__.py
Normal file
17
app/utils/slice_engines/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from .cura_engine import CuraEngine
|
||||||
|
from .prusa_slicer_engine import PrusaSlicerEngine
|
||||||
|
|
||||||
|
def get_slicer_engine(engine_name="cura"):
|
||||||
|
"""
|
||||||
|
Factory function to retrieve the requested slicing engine instance.
|
||||||
|
Valid names: 'cura', 'prusa_slicer'
|
||||||
|
"""
|
||||||
|
engine_name = engine_name.lower().strip()
|
||||||
|
|
||||||
|
if engine_name in ['cura', 'cura_engine', 'curaengine']:
|
||||||
|
return CuraEngine()
|
||||||
|
elif engine_name in ['prusa', 'prusa_slicer', 'prusaslicer']:
|
||||||
|
return PrusaSlicerEngine()
|
||||||
|
else:
|
||||||
|
# Default fallback
|
||||||
|
return CuraEngine()
|
||||||
@@ -1,49 +1,28 @@
|
|||||||
from huey import SqliteHuey
|
|
||||||
import subprocess
|
|
||||||
import os
|
import os
|
||||||
from app.models import db, PrintFile, SystemConfig
|
import subprocess
|
||||||
from app.utils.conf_parse import ConfParse
|
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import configparser
|
import configparser
|
||||||
|
from app.utils.conf_parse import ConfParse
|
||||||
|
|
||||||
|
class CuraEngine:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = "cura"
|
||||||
|
|
||||||
import os
|
def slice(self, app, stl_filepath, gcode_filepath, **kwargs):
|
||||||
|
"""
|
||||||
# Ensure instance directory exists
|
Slices via CuraEngine.
|
||||||
instance_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'instance')
|
Returns (success_bool, error_msg_if_any)
|
||||||
os.makedirs(instance_dir, exist_ok=True)
|
"""
|
||||||
huey_db_path = os.path.join(instance_dir, 'huey_queue.db')
|
quality_preset = kwargs.get('quality_preset')
|
||||||
|
infill_density = kwargs.get('infill_density')
|
||||||
huey = SqliteHuey(filename=huey_db_path)
|
support_enable = kwargs.get('support_enable')
|
||||||
|
support_pattern = kwargs.get('support_pattern')
|
||||||
|
|
||||||
@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):
|
|
||||||
# This is run by the Huey worker
|
|
||||||
# We need to create an app context to interact with the database
|
|
||||||
from app import create_app
|
|
||||||
app = create_app()
|
|
||||||
with app.app_context():
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
|
||||||
if not print_file:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Cache variables and commit slicing status
|
|
||||||
gcode_filename = print_file.filename.rsplit('.', 1)[0] + '.gcode'
|
|
||||||
gcode_filepath = os.path.join(app.config['UPLOAD_FOLDER'], gcode_filename)
|
|
||||||
print_file.status = 'slicing'
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Remove DB session to avoid locking the sqlite db during long slicing operations
|
|
||||||
db.session.remove()
|
|
||||||
|
|
||||||
tmp_def_path = None
|
tmp_def_path = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create Cura engine options
|
base_config_path = os.path.abspath(os.path.join(app.root_path, '..', 'print_config'))
|
||||||
# use our local minimal configurations detached from the entire Cura framework
|
print_config_path = os.path.join(base_config_path, 'cura_engine')
|
||||||
print_config_path = os.path.abspath(os.path.join(app.root_path, '..', 'print_config'))
|
|
||||||
printers_path = os.path.join(print_config_path, 'printers')
|
printers_path = os.path.join(print_config_path, 'printers')
|
||||||
extruders_path = os.path.join(print_config_path, 'extruders')
|
extruders_path = os.path.join(print_config_path, 'extruders')
|
||||||
materials_path = os.path.join(print_config_path, 'materials')
|
materials_path = os.path.join(print_config_path, 'materials')
|
||||||
@@ -61,6 +40,8 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
]
|
]
|
||||||
|
|
||||||
inst_files_list = []
|
inst_files_list = []
|
||||||
|
quality_type = None
|
||||||
|
preset_path = None
|
||||||
|
|
||||||
if quality_preset:
|
if quality_preset:
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
@@ -90,10 +71,9 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
g_path = os.path.join(presets_path, 'creality', 'globals', f"{quality_type}.inst.cfg")
|
g_path = os.path.join(presets_path, 'creality', 'globals', f"{quality_type}.inst.cfg")
|
||||||
if os.path.exists(g_path): inst_files_list.append(g_path)
|
if os.path.exists(g_path): inst_files_list.append(g_path)
|
||||||
|
|
||||||
if quality_preset and os.path.exists(preset_path):
|
if quality_preset and preset_path and os.path.exists(preset_path):
|
||||||
inst_files_list.append(preset_path)
|
inst_files_list.append(preset_path)
|
||||||
|
|
||||||
|
|
||||||
p = ConfParse(def_files)
|
p = ConfParse(def_files)
|
||||||
settings_with_inst = p.add_inst_cfg(inst_files_list)
|
settings_with_inst = p.add_inst_cfg(inst_files_list)
|
||||||
|
|
||||||
@@ -112,13 +92,12 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
if support_pattern == 'tree':
|
if support_pattern == 'tree':
|
||||||
if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
|
if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
|
||||||
settings_with_inst["support_structure"]["value"] = "'tree'"
|
settings_with_inst["support_structure"]["value"] = "'tree'"
|
||||||
elif support_pattern in settings_with_inst["support_pattern"]["options"].keys():
|
elif support_pattern and "support_pattern" in settings_with_inst and "options" in settings_with_inst["support_pattern"] and support_pattern in settings_with_inst["support_pattern"]["options"].keys():
|
||||||
if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
|
if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
|
||||||
settings_with_inst["support_structure"]["value"] = "'normal'"
|
settings_with_inst["support_structure"]["value"] = "'normal'"
|
||||||
if "support_pattern" not in settings_with_inst: settings_with_inst["support_pattern"] = {}
|
if "support_pattern" not in settings_with_inst: settings_with_inst["support_pattern"] = {}
|
||||||
settings_with_inst["support_pattern"]["value"] = f"'{support_pattern}'"
|
settings_with_inst["support_pattern"]["value"] = f"'{support_pattern}'"
|
||||||
|
|
||||||
# Parse to exact values
|
|
||||||
res = p.parse_configs(settings_with_inst)
|
res = p.parse_configs(settings_with_inst)
|
||||||
|
|
||||||
override_dict = {}
|
override_dict = {}
|
||||||
@@ -126,15 +105,10 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
if v.get("enabled", True):
|
if v.get("enabled", True):
|
||||||
val = v.get("value", None)
|
val = v.get("value", None)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
# Filter out our protective ConfigStr wrappers
|
|
||||||
# if type(val).__name__ == "ConfigStr": pass
|
|
||||||
# else: override_dict[k] = {"default_value": val}
|
|
||||||
override_dict[k] = {"value": val, "default_value": val}
|
override_dict[k] = {"value": val, "default_value": val}
|
||||||
elif "default_value" in v:
|
elif "default_value" in v:
|
||||||
override_dict[k] = {"default_value": v["default_value"], "value": v["default_value"]}
|
override_dict[k] = {"default_value": v["default_value"], "value": v["default_value"]}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
tmp_def_filename = f"tmp_{uuid.uuid4().hex}.def.json"
|
tmp_def_filename = f"tmp_{uuid.uuid4().hex}.def.json"
|
||||||
tmp_def_path = os.path.join(app.config['UPLOAD_FOLDER'], tmp_def_filename)
|
tmp_def_path = os.path.join(app.config['UPLOAD_FOLDER'], tmp_def_filename)
|
||||||
|
|
||||||
@@ -152,11 +126,9 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
"has_variants": True,
|
"has_variants": True,
|
||||||
"has_machine_quality": True,
|
"has_machine_quality": True,
|
||||||
"variants_name": "Nozzle Size",
|
"variants_name": "Nozzle Size",
|
||||||
|
|
||||||
"preferred_variant_name": "0.4mm Nozzle",
|
"preferred_variant_name": "0.4mm Nozzle",
|
||||||
"preferred_quality_type": "standard",
|
"preferred_quality_type": "standard",
|
||||||
"preferred_material": "generic_pla",
|
"preferred_material": "generic_pla",
|
||||||
|
|
||||||
},
|
},
|
||||||
"overrides": override_dict
|
"overrides": override_dict
|
||||||
}
|
}
|
||||||
@@ -174,124 +146,23 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
]
|
]
|
||||||
|
|
||||||
app.logger.info(f"Running command: {' '.join(command)}")
|
app.logger.info(f"Running command: {' '.join(command)}")
|
||||||
# print(f"Running command: {' '.join(command)}")
|
|
||||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
||||||
stdout, stderr = process.communicate()
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
# if stdout:
|
|
||||||
# print(f"[CuraEngine STDOUT]\n{stdout.decode('utf-8', errors='ignore')}")
|
|
||||||
# if stderr:
|
|
||||||
# print(f"[CuraEngine STDERR]\n{stderr.decode('utf-8', errors='ignore')}", flush=True)
|
|
||||||
|
|
||||||
# Re-fetch print_file and update status
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
|
||||||
if not print_file:
|
|
||||||
return
|
|
||||||
|
|
||||||
if process.returncode == 0:
|
if process.returncode == 0:
|
||||||
print_file.status = 'sliced'
|
return True, None
|
||||||
else:
|
else:
|
||||||
print_file.status = 'failed'
|
err_msg = stderr.decode() if stderr else "Unknown CuraEngine error"
|
||||||
app.logger.error(f"CuraEngine Error: {stderr.decode()}")
|
app.logger.error(f"CuraEngine Error: {err_msg}")
|
||||||
|
return False, err_msg
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Re-fetch in case of exception
|
app.logger.error(f"CuraEngine Exception: {e}")
|
||||||
print_file = PrintFile.query.get(file_id)
|
return False, str(e)
|
||||||
if print_file:
|
|
||||||
print_file.status = 'failed'
|
|
||||||
app.logger.error(f"Subprocess Exception: {e}")
|
|
||||||
|
|
||||||
if delete_stl and os.path.exists(stl_filepath):
|
|
||||||
try:
|
|
||||||
os.remove(stl_filepath)
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"Failed to delete temp STL {stl_filepath}: {e}")
|
|
||||||
|
|
||||||
|
finally:
|
||||||
if tmp_def_path and os.path.exists(tmp_def_path):
|
if tmp_def_path and os.path.exists(tmp_def_path):
|
||||||
try:
|
try:
|
||||||
os.remove(tmp_def_path)
|
os.remove(tmp_def_path)
|
||||||
# pass
|
|
||||||
except Exception as e:
|
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}")
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
db.session.remove()
|
|
||||||
|
|
||||||
|
|
||||||
@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):
|
|
||||||
from app import create_app
|
|
||||||
app = create_app()
|
|
||||||
with app.app_context():
|
|
||||||
from app.models import PrintFile, db
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
|
||||||
if not print_file:
|
|
||||||
return
|
|
||||||
|
|
||||||
db.session.remove()
|
|
||||||
|
|
||||||
try:
|
|
||||||
from app.utils.stl_merger import merge_stls
|
|
||||||
merge_stls(inputs, merged_filepath)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
except Exception as e:
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
|
||||||
if print_file:
|
|
||||||
print_file.status = 'failed'
|
|
||||||
db.session.commit()
|
|
||||||
app.logger.error(f"Merge Exception: {e}")
|
|
||||||
finally:
|
|
||||||
db.session.remove()
|
|
||||||
|
|
||||||
@huey.task()
|
|
||||||
def simplify_stl_task(file_id, filepath):
|
|
||||||
from app import create_app
|
|
||||||
app = create_app()
|
|
||||||
with app.app_context():
|
|
||||||
from app.models import PrintFile, SystemConfig, db
|
|
||||||
import os
|
|
||||||
from app.utils.stl_simplifier import simplify_stl
|
|
||||||
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
|
||||||
if not print_file:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
file_size_mb = os.path.getsize(filepath) / (1024 * 1024)
|
|
||||||
|
|
||||||
configs = {c.key: c.value for c in SystemConfig.query.all()}
|
|
||||||
skip_size = float(configs.get('proxy_skip_size_mb', '5.0'))
|
|
||||||
|
|
||||||
proxy_path = filepath + '.proxy.stl'
|
|
||||||
|
|
||||||
if file_size_mb <= skip_size:
|
|
||||||
# File is small enough, no proxy needed
|
|
||||||
print_file.status = 'uploaded'
|
|
||||||
db.session.commit()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Aim for approx 7.5 MB for the proxy
|
|
||||||
target_mb = 7.5
|
|
||||||
keep_ratio = target_mb / file_size_mb
|
|
||||||
if keep_ratio > 1.0:
|
|
||||||
keep_ratio = 1.0
|
|
||||||
elif keep_ratio < 0.01:
|
|
||||||
keep_ratio = 0.01
|
|
||||||
|
|
||||||
app.logger.info(f"Simplifying {filepath}... Size: {file_size_mb:.2f}MB, Target Ratio: {keep_ratio:.3f}")
|
|
||||||
|
|
||||||
simplify_stl(filepath, proxy_path, keep_ratio=keep_ratio)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"Simplify task error: {e}")
|
|
||||||
|
|
||||||
# Update status to uploaded regardless of success or failure of proxy generation
|
|
||||||
# So the user can still slice or download it
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
|
||||||
if print_file:
|
|
||||||
print_file.status = 'uploaded'
|
|
||||||
db.session.commit()
|
|
||||||
db.session.remove()
|
|
||||||
57
app/utils/slice_engines/prusa_slicer_engine.py
Normal file
57
app/utils/slice_engines/prusa_slicer_engine.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
class PrusaSlicerEngine:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = "prusa_slicer"
|
||||||
|
|
||||||
|
def slice(self, app, stl_filepath, gcode_filepath, **kwargs):
|
||||||
|
"""
|
||||||
|
Slices via prusa-slicer CLI mapping standard kwargs to PRUSA parameters where possible.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Base command
|
||||||
|
command = [
|
||||||
|
"prusa-slicer",
|
||||||
|
"-g", stl_filepath,
|
||||||
|
"--output", gcode_filepath
|
||||||
|
]
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
infill_density = kwargs.get('infill_density')
|
||||||
|
support_enable = kwargs.get('support_enable')
|
||||||
|
support_pattern = kwargs.get('support_pattern')
|
||||||
|
|
||||||
|
if infill_density is not None:
|
||||||
|
command.extend(["--fill-density", f"{infill_density}%"])
|
||||||
|
|
||||||
|
if support_enable and support_enable != 'false':
|
||||||
|
command.append("--support-material")
|
||||||
|
if support_enable == 'buildplate':
|
||||||
|
command.append("--support-material-buildplate-only")
|
||||||
|
# PrusaSlicer equivalent for tree supports => organic
|
||||||
|
if support_pattern == 'tree':
|
||||||
|
command.extend(["--support-material-style", "organic"])
|
||||||
|
elif support_pattern:
|
||||||
|
pass # mapped to default/grid in prusa CLI without explicit config
|
||||||
|
else:
|
||||||
|
pass # Prusa defaults to no supports unless specified
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
app.logger.info(f"Running command: {' '.join(command)}")
|
||||||
|
|
||||||
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
return True, None
|
||||||
|
else:
|
||||||
|
err_msg = stderr.decode() if stderr else "Unknown prusa-slicer error"
|
||||||
|
app.logger.error(f"PrusaSlicer Error: {err_msg}")
|
||||||
|
return False, err_msg
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error(f"PrusaSlicer Exception: {e}")
|
||||||
|
return False, str(e)
|
||||||
@@ -45,182 +45,51 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, infill_density=No
|
|||||||
# Remove DB session to avoid locking the sqlite db during long slicing operations
|
# Remove DB session to avoid locking the sqlite db during long slicing operations
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
|
|
||||||
tmp_def_path = None
|
from app.utils.slice_engines import get_slicer_engine
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create Cura engine options
|
# Optionally fetch the preferred engine from db conf or just default to cura
|
||||||
# use our local minimal configurations detached from the entire Cura framework
|
# For now default to cura or whichever is passed via kwargs if implemented later
|
||||||
print_config_path = os.path.abspath(os.path.join(app.root_path, '..', 'print_config'))
|
conf_engine = SystemConfig.query.filter_by(key='slicer_engine').first()
|
||||||
printers_path = os.path.join(print_config_path, 'printers')
|
engine_name = conf_engine.value if conf_engine and conf_engine.value else "cura"
|
||||||
extruders_path = os.path.join(print_config_path, 'extruders')
|
db.session.remove()
|
||||||
materials_path = os.path.join(print_config_path, 'materials')
|
|
||||||
presets_path = os.path.join(print_config_path, 'quality')
|
|
||||||
variants_path = os.path.join(print_config_path, 'variants')
|
|
||||||
|
|
||||||
env = os.environ.copy()
|
slicer = get_slicer_engine(engine_name)
|
||||||
env["CURA_ENGINE_SEARCH_PATH"] = f"{printers_path}:{extruders_path}:{materials_path}:{presets_path}:{variants_path}"
|
|
||||||
|
|
||||||
def_files = [
|
success, err_msg = slicer.slice(
|
||||||
os.path.join(printers_path, "fdmprinter.def.json"),
|
app=app,
|
||||||
os.path.join(printers_path, "fdmextruder.def.json"),
|
stl_filepath=stl_filepath,
|
||||||
os.path.join(printers_path, "creality_base.def.json"),
|
gcode_filepath=gcode_filepath,
|
||||||
os.path.join(printers_path, "creality_ender3v3se.def.json")
|
quality_preset=quality_preset,
|
||||||
]
|
infill_density=infill_density,
|
||||||
|
support_enable=support_enable,
|
||||||
inst_files_list = []
|
support_pattern=support_pattern
|
||||||
|
)
|
||||||
if quality_preset:
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
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)
|
|
||||||
variant_type = config.get('metadata', 'variant', fallback=None)
|
|
||||||
quality_type = config.get('metadata', 'quality_type', fallback=None)
|
|
||||||
|
|
||||||
if material_type:
|
|
||||||
m_path = os.path.join(materials_path, f"{material_type}.inst.cfg")
|
|
||||||
if os.path.exists(m_path): inst_files_list.append(m_path)
|
|
||||||
if variant_type:
|
|
||||||
variant_d = variant_type.split("mm")[0]
|
|
||||||
v_path = os.path.join(variants_path, "creality", f"creality_ender3v3se_{variant_d}.inst.cfg")
|
|
||||||
if os.path.exists(v_path): inst_files_list.append(v_path)
|
|
||||||
|
|
||||||
if support_pattern == 'tree':
|
|
||||||
t_path = os.path.join(print_config_path, 'supports', 'tree.inst.cfg')
|
|
||||||
if os.path.exists(t_path): inst_files_list.append(t_path)
|
|
||||||
elif support_pattern and support_pattern != 'false':
|
|
||||||
n_path = os.path.join(print_config_path, 'supports', 'normal.inst.cfg')
|
|
||||||
if os.path.exists(n_path): inst_files_list.append(n_path)
|
|
||||||
|
|
||||||
if quality_preset and quality_type:
|
|
||||||
g_path = os.path.join(presets_path, 'creality', 'globals', f"{quality_type}.inst.cfg")
|
|
||||||
if os.path.exists(g_path): inst_files_list.append(g_path)
|
|
||||||
|
|
||||||
if quality_preset and os.path.exists(preset_path):
|
|
||||||
inst_files_list.append(preset_path)
|
|
||||||
|
|
||||||
|
|
||||||
p = ConfParse(def_files)
|
|
||||||
settings_with_inst = p.add_inst_cfg(inst_files_list)
|
|
||||||
|
|
||||||
if infill_density is not None:
|
|
||||||
if "infill_sparse_density" not in settings_with_inst: settings_with_inst["infill_sparse_density"] = {}
|
|
||||||
settings_with_inst["infill_sparse_density"]["value"] = str(infill_density)
|
|
||||||
if "infill_line_distance" not in settings_with_inst: settings_with_inst["infill_line_distance"] = {}
|
|
||||||
settings_with_inst["infill_line_distance"]["value"] = str(100 / int(infill_density)) if int(infill_density) > 0 else "9999"
|
|
||||||
|
|
||||||
if support_enable is not None:
|
|
||||||
if "support_enable" not in settings_with_inst: settings_with_inst["support_enable"] = {}
|
|
||||||
settings_with_inst["support_enable"]["value"] = True if support_enable in ['true', 'buildplate'] else False
|
|
||||||
if "support_type" not in settings_with_inst: settings_with_inst["support_type"] = {}
|
|
||||||
settings_with_inst["support_type"]["value"] = "'buildplate'" if support_enable == 'buildplate' else "'everywhere'"
|
|
||||||
|
|
||||||
if support_pattern == 'tree':
|
|
||||||
if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
|
|
||||||
settings_with_inst["support_structure"]["value"] = "'tree'"
|
|
||||||
elif support_pattern in settings_with_inst["support_pattern"]["options"].keys():
|
|
||||||
if "support_structure" not in settings_with_inst: settings_with_inst["support_structure"] = {}
|
|
||||||
settings_with_inst["support_structure"]["value"] = "'normal'"
|
|
||||||
if "support_pattern" not in settings_with_inst: settings_with_inst["support_pattern"] = {}
|
|
||||||
settings_with_inst["support_pattern"]["value"] = f"'{support_pattern}'"
|
|
||||||
|
|
||||||
# Parse to exact values
|
|
||||||
res = p.parse_configs(settings_with_inst)
|
|
||||||
|
|
||||||
override_dict = {}
|
|
||||||
for k, v in res.items():
|
|
||||||
if v.get("enabled", True):
|
|
||||||
val = v.get("value", None)
|
|
||||||
if val is not None:
|
|
||||||
# Filter out our protective ConfigStr wrappers
|
|
||||||
# if type(val).__name__ == "ConfigStr": pass
|
|
||||||
# else: override_dict[k] = {"default_value": val}
|
|
||||||
override_dict[k] = {"value": val,"default_value": val}
|
|
||||||
elif "default_value" in v:
|
|
||||||
override_dict[k] = {"default_value": v["default_value"], "value": v["default_value"]}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
tmp_def_filename = f"tmp_{uuid.uuid4().hex}.def.json"
|
|
||||||
tmp_def_path = os.path.join(app.config['UPLOAD_FOLDER'], tmp_def_filename)
|
|
||||||
|
|
||||||
tmp_def_obj = {
|
|
||||||
"version": 2,
|
|
||||||
"name": "TempProfile",
|
|
||||||
"inherits": "fdmprinter",
|
|
||||||
"metadata": {
|
|
||||||
"visible": True,
|
|
||||||
"author": "System",
|
|
||||||
"manufacturer": "System",
|
|
||||||
"file_formats": "text/x-gcode",
|
|
||||||
"first_start_actions": ["MachineSettingsAction"],
|
|
||||||
"has_materials": True,
|
|
||||||
"has_variants": True,
|
|
||||||
"has_machine_quality": True,
|
|
||||||
"variants_name": "Nozzle Size",
|
|
||||||
|
|
||||||
"preferred_variant_name": "0.4mm Nozzle",
|
|
||||||
"preferred_quality_type": "standard",
|
|
||||||
"preferred_material": "generic_pla",
|
|
||||||
|
|
||||||
},
|
|
||||||
"overrides": override_dict
|
|
||||||
}
|
|
||||||
|
|
||||||
pretty_json = json.dumps(tmp_def_obj, indent=4)
|
|
||||||
|
|
||||||
with open(tmp_def_path, "w") as f:
|
|
||||||
f.write(pretty_json)
|
|
||||||
|
|
||||||
command = [
|
|
||||||
"CuraEngine", "slice",
|
|
||||||
"-j", tmp_def_path,
|
|
||||||
"-l", stl_filepath,
|
|
||||||
"-o", gcode_filepath
|
|
||||||
]
|
|
||||||
|
|
||||||
app.logger.info(f"Running command: {' '.join(command)}")
|
|
||||||
# print(f"Running command: {' '.join(command)}")
|
|
||||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
|
||||||
stdout, stderr = process.communicate()
|
|
||||||
|
|
||||||
# if stdout:
|
|
||||||
# print(f"[CuraEngine STDOUT]\n{stdout.decode('utf-8', errors='ignore')}")
|
|
||||||
# if stderr:
|
|
||||||
# print(f"[CuraEngine STDERR]\n{stderr.decode('utf-8', errors='ignore')}", flush=True)
|
|
||||||
|
|
||||||
# Re-fetch print_file and update status
|
# Re-fetch print_file and update status
|
||||||
print_file = PrintFile.query.get(file_id)
|
print_file = PrintFile.query.get(file_id)
|
||||||
if not print_file:
|
if not print_file:
|
||||||
return
|
return
|
||||||
|
|
||||||
if process.returncode == 0:
|
if success:
|
||||||
print_file.status = 'sliced'
|
print_file.status = 'sliced'
|
||||||
else:
|
else:
|
||||||
print_file.status = 'failed'
|
print_file.status = 'failed'
|
||||||
app.logger.error(f"CuraEngine Error: {stderr.decode()}")
|
app.logger.error(f"Slicing Task Failed: {err_msg}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Re-fetch in case of exception
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
print_file = PrintFile.query.get(file_id)
|
||||||
if print_file:
|
if print_file:
|
||||||
print_file.status = 'failed'
|
print_file.status = 'failed'
|
||||||
app.logger.error(f"Subprocess Exception: {e}")
|
app.logger.error(f"Subprocess Exception: {e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
if delete_stl and os.path.exists(stl_filepath):
|
if delete_stl and os.path.exists(stl_filepath):
|
||||||
try:
|
try:
|
||||||
os.remove(stl_filepath)
|
os.remove(stl_filepath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.error(f"Failed to delete temp STL {stl_filepath}: {e}")
|
app.logger.error(f"Failed to delete temp STL {stl_filepath}: {e}")
|
||||||
|
|
||||||
if tmp_def_path and os.path.exists(tmp_def_path):
|
|
||||||
try:
|
|
||||||
os.remove(tmp_def_path)
|
|
||||||
# pass
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"Failed to delete temp JSON config {tmp_def_path}: {e}")
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
|
|
||||||
|
|||||||
62
patch_tasks.py
Normal file
62
patch_tasks.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
with open('app/utils/tasks.py', 'r', encoding='utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
import re
|
||||||
|
old_block = re.search(r'tmp_def_path = None.*?db\.session\.remove\(\)', text, re.DOTALL)
|
||||||
|
|
||||||
|
new_block = """from app.utils.slice_engines import get_slicer_engine
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Optionally fetch the preferred engine from db conf or just default to cura
|
||||||
|
# For now default to cura or whichever is passed via kwargs if implemented later
|
||||||
|
conf_engine = SystemConfig.query.filter_by(key='slicer_engine').first()
|
||||||
|
engine_name = conf_engine.value if conf_engine and conf_engine.value else "cura"
|
||||||
|
db.session.remove()
|
||||||
|
|
||||||
|
slicer = get_slicer_engine(engine_name)
|
||||||
|
|
||||||
|
success, err_msg = slicer.slice(
|
||||||
|
app=app,
|
||||||
|
stl_filepath=stl_filepath,
|
||||||
|
gcode_filepath=gcode_filepath,
|
||||||
|
quality_preset=quality_preset,
|
||||||
|
infill_density=infill_density,
|
||||||
|
support_enable=support_enable,
|
||||||
|
support_pattern=support_pattern
|
||||||
|
)
|
||||||
|
|
||||||
|
# Re-fetch print_file and update status
|
||||||
|
print_file = PrintFile.query.get(file_id)
|
||||||
|
if not print_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print_file.status = 'sliced'
|
||||||
|
else:
|
||||||
|
print_file.status = 'failed'
|
||||||
|
app.logger.error(f"Slicing Task Failed: {err_msg}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print_file = PrintFile.query.get(file_id)
|
||||||
|
if print_file:
|
||||||
|
print_file.status = 'failed'
|
||||||
|
app.logger.error(f"Subprocess Exception: {e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if delete_stl and os.path.exists(stl_filepath):
|
||||||
|
try:
|
||||||
|
os.remove(stl_filepath)
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error(f"Failed to delete temp STL {stl_filepath}: {e}")
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
db.session.remove()"""
|
||||||
|
|
||||||
|
if old_block:
|
||||||
|
res = text.replace(old_block.group(0), new_block)
|
||||||
|
with open('app/utils/tasks.py', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(res)
|
||||||
|
print("Patched successfully")
|
||||||
|
else:
|
||||||
|
print("Pattern not found!")
|
||||||
|
|
||||||
Reference in New Issue
Block a user