import subprocess import os import json import uuid import configparser from huey import SqliteHuey from app import create_app from app.models import db, PrintFile, SystemConfig from app.utils.conf_parse import ConfParse from app.utils.slice_engines import get_slicer_engine from app.utils.stl_merger import merge_stls from app.utils.stl_simplifier import simplify_stl # Ensure instance directory exists instance_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'instance') os.makedirs(instance_dir, exist_ok=True) huey_db_path = os.path.join(instance_dir, 'huey_queue.db') huey = SqliteHuey(filename=huey_db_path) def get_gcode_dir(app): with app.app_context(): conf = SystemConfig.query.filter_by(key='gcode_upload_folder').first() if conf and conf.value and os.path.exists(conf.value): return conf.value return app.config['UPLOAD_FOLDER'] @huey.task() 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 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(get_gcode_dir(app), 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() try: # Optionally fetch the preferred engine from db conf or just default to prusa # For now default to prusa 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 "prusa" db.session.remove() slicer = get_slicer_engine(engine_name,app.config['PRINT_CONFIG_FOLDER']) success, err_msg = slicer.slice( app=app, 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 ) # 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() @huey.task() 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): app = create_app() with app.app_context(): print_file = PrintFile.query.get(file_id) if not print_file: return db.session.remove() try: 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, 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: 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): app = create_app() with app.app_context(): 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()