from huey import SqliteHuey import subprocess import os from .models import db, PrintFile, SystemConfig from .conf_parse import ConfParse import json import uuid import configparser huey = SqliteHuey(filename='huey_queue.db') @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 try: # Create Cura engine options # use our local minimal configurations detached from the entire Cura framework print_config_path = os.path.abspath(os.path.join(app.root_path, '..', 'print_config')) printers_path = os.path.join(print_config_path, 'printers') extruders_path = os.path.join(print_config_path, 'extruders') 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() env["CURA_ENGINE_SEARCH_PATH"] = f"{printers_path}:{extruders_path}:{materials_path}:{presets_path}:{variants_path}" def_files = [ os.path.join(printers_path, "fdmprinter.def.json"), os.path.join(printers_path, "fdmextruder.def.json"), os.path.join(printers_path, "creality_base.def.json"), os.path.join(printers_path, "creality_ender3v3se.def.json") ] inst_files_list = [] 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 print_file = PrintFile.query.get(file_id) if not print_file: return if process.returncode == 0: print_file.status = 'sliced' else: print_file.status = 'failed' app.logger.error(f"CuraEngine Error: {stderr.decode()}") except Exception as e: # Re-fetch in case of exception print_file = PrintFile.query.get(file_id) 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}") 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.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 .models import PrintFile, db print_file = PrintFile.query.get(file_id) if not print_file: return db.session.remove() try: from 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()