@@ -47,6 +47,7 @@ def settings():
|
||||
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', '')
|
||||
default_printer = request.form.get('default_printer', '')
|
||||
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()
|
||||
@@ -61,6 +62,7 @@ def settings():
|
||||
('default_support_pattern', default_support_pattern),
|
||||
('default_quality', default_quality),
|
||||
('default_material', default_material),
|
||||
('default_printer', default_printer),
|
||||
('gcode_upload_folder', gcode_upload_folder),
|
||||
('slicer_engine', slicer_engine),
|
||||
('build_plate_model_path', build_plate_model_path),
|
||||
@@ -187,15 +189,5 @@ def delete_user(user_id):
|
||||
flash(f'User {user.username} and all their files have been deleted.', 'success')
|
||||
return redirect(url_for('admin.users'))
|
||||
|
||||
def get_bed_dimensions():
|
||||
try:
|
||||
path = os.path.join(current_app.root_path, '..', 'print_config', 'printers', 'creality_ender3v3se.def.json')
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
w = data['overrides']['machine_width']['default_value']
|
||||
h = data['overrides']['machine_depth']['default_value']
|
||||
hd = data['overrides']['machine_height']['default_value']
|
||||
return w, h, hd
|
||||
except:
|
||||
return 200, 200, 200
|
||||
|
||||
|
||||
|
||||
@@ -14,19 +14,19 @@ from app import i18n_dict
|
||||
# import trimesh.repair
|
||||
from app.utils.stl_simplifier import simplify_stl
|
||||
from app.routes.admin_routes import get_gcode_dir
|
||||
|
||||
from app.utils.slice_engines import get_slicer_engine
|
||||
from app.models import UserSession
|
||||
from flask_login import logout_user
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
|
||||
@main_bp.before_app_request
|
||||
def check_user_session():
|
||||
if current_user.is_authenticated and not current_user.is_guest:
|
||||
from app.models import UserSession
|
||||
session_token = session.get('user_session_token')
|
||||
if session_token:
|
||||
user_session = UserSession.query.filter_by(session_token=session_token).first()
|
||||
if not user_session or not user_session.is_active:
|
||||
from flask_login import logout_user
|
||||
logout_user()
|
||||
session.pop('user_session_token', None)
|
||||
flash('Your session has been terminated.', 'warning')
|
||||
@@ -305,8 +305,11 @@ def preview_gcode(file_id):
|
||||
content = "".join(lines[:500]) # Preview first 500 lines
|
||||
if line_count > 500:
|
||||
content += f"\n... \n[Preview truncated. Total lines: {line_count}. Please download to view full file.]"
|
||||
|
||||
w, h, hd = get_bed_dimensions()
|
||||
|
||||
engine_name = SystemConfig.query.filter_by(key='slicer_engine').first()
|
||||
if engine_name:
|
||||
engine = get_slicer_engine(str(engine_name.value), current_app.config['PRINT_CONFIG_FOLDER'])
|
||||
w, h, hd = engine.get_bed_dimensions()
|
||||
configs = {c.key: c.value for c in SystemConfig.query.all()}
|
||||
offset_x = float(configs.get('offset_x', '0.0'))
|
||||
offset_y = float(configs.get('offset_y', '0.0'))
|
||||
@@ -344,25 +347,17 @@ def delete_file(file_id):
|
||||
|
||||
# --- Auth Routes ---
|
||||
|
||||
def get_bed_dimensions():
|
||||
try:
|
||||
path = os.path.join(current_app.root_path, '..', 'print_config', 'printers', 'creality_ender3v3se.def.json')
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
w = data['overrides']['machine_width']['default_value']
|
||||
h = data['overrides']['machine_depth']['default_value']
|
||||
hd = data['overrides']['machine_height']['default_value']
|
||||
return w, h, hd
|
||||
except:
|
||||
return 200, 200, 200
|
||||
|
||||
@main_bp.route('/plater')
|
||||
@login_required
|
||||
def plater():
|
||||
quota_mb, current_size = get_quota_info(current_user, 'gcode')
|
||||
quota_exceeded = (quota_mb > 0 and current_size >= quota_mb * 1024 * 1024)
|
||||
|
||||
w, h, hd = get_bed_dimensions()
|
||||
engine_name = SystemConfig.query.filter_by(key='slicer_engine').first()
|
||||
if engine_name:
|
||||
engine = get_slicer_engine(str(engine_name.value), current_app.config['PRINT_CONFIG_FOLDER'])
|
||||
w, h, hd = engine.get_bed_dimensions()
|
||||
print(f"Bed dimensions: {w}x{h}x{hd}")
|
||||
|
||||
|
||||
configs = {c.key: c.value for c in SystemConfig.query.all()}
|
||||
@@ -574,12 +569,12 @@ def build_plate_model():
|
||||
@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(current_app)
|
||||
materials = engine.get_materials(current_app) if hasattr(engine, 'get_materials') else []
|
||||
return jsonify({'presets': presets, 'support_patterns': patterns, 'materials': materials})
|
||||
engine = get_slicer_engine(engine_name, current_app.config['PRINT_CONFIG_FOLDER'])
|
||||
presets = engine.get_quality_presets()
|
||||
patterns = engine.get_support_patterns()
|
||||
materials = engine.get_materials() if hasattr(engine, 'get_materials') else []
|
||||
printers = engine.get_all_printers() if hasattr(engine, 'get_all_printers') else []
|
||||
return jsonify({'presets': presets, 'support_patterns': patterns, 'materials': materials, 'printers': printers})
|
||||
|
||||
@main_bp.route('/account', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
|
||||
@@ -19,6 +19,17 @@ def get_octo_client():
|
||||
return OctoPrintClient(url.value, apikey.value)
|
||||
return None
|
||||
|
||||
def _enrich_job_data(job_data):
|
||||
if job_data and job_data.get('job', {}).get('file', {}).get('name'):
|
||||
from app.models import PrintFile
|
||||
internal_name = job_data['job']['file']['name']
|
||||
print_file = PrintFile.query.filter_by(filename=internal_name).first()
|
||||
if print_file and print_file.original_filename:
|
||||
job_data['job']['file']['display_name'] = print_file.original_filename
|
||||
else:
|
||||
job_data['job']['file']['display_name'] = internal_name
|
||||
return job_data
|
||||
|
||||
@printer_bp.route('/status')
|
||||
@login_required
|
||||
def status():
|
||||
@@ -29,7 +40,7 @@ def status():
|
||||
if client:
|
||||
try:
|
||||
status_data = client.get_printer_status()
|
||||
job_data = client.get_job_info()
|
||||
job_data = _enrich_job_data(client.get_job_info())
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
else:
|
||||
@@ -37,6 +48,20 @@ def status():
|
||||
|
||||
return render_template('printer/status.html', status=status_data, job=job_data, error=error)
|
||||
|
||||
@printer_bp.route('/api/status_data')
|
||||
@login_required
|
||||
def api_status_data():
|
||||
client = get_octo_client()
|
||||
if client:
|
||||
try:
|
||||
status_data = client.get_printer_status()
|
||||
job_data = _enrich_job_data(client.get_job_info())
|
||||
return jsonify({'success': True, 'status': status_data, 'job': job_data})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
return jsonify({'success': False, 'error': 'OctoPrint is not configured.'})
|
||||
|
||||
|
||||
def get_gcode_dir():
|
||||
conf = SystemConfig.query.filter_by(key='gcode_upload_folder').first()
|
||||
if conf and conf.value and os.path.exists(conf.value):
|
||||
@@ -102,13 +127,45 @@ def prepare():
|
||||
|
||||
return render_template('printer/prepare.html', files=files, error=error)
|
||||
|
||||
|
||||
def check_printer_control_permission(client):
|
||||
from flask_login import current_user
|
||||
if current_user.is_admin:
|
||||
return True, None
|
||||
|
||||
try:
|
||||
status_data = client.get_printer_status()
|
||||
state = status_data.get('state', {}).get('text', '')
|
||||
active_states = ['Printing', 'Paused', 'Pausing', 'Resuming', 'Cancelling']
|
||||
if state not in active_states:
|
||||
return True, None
|
||||
|
||||
job_info = client.get_job_info()
|
||||
internal_name = job_info.get('job', {}).get('file', {}).get('name')
|
||||
if not internal_name:
|
||||
return False, "现在有任务正在运行,非管理员无法进行控制。"
|
||||
|
||||
from app.models import PrintFile
|
||||
pf = PrintFile.query.filter_by(filename=internal_name).first()
|
||||
if pf and pf.user_id == current_user.id:
|
||||
return True, None
|
||||
else:
|
||||
return False, "现在有任务正在运行,您无权进行此操作。只有管理员或任务发起者可以进行控制。"
|
||||
except Exception:
|
||||
pass
|
||||
return True, None
|
||||
|
||||
@printer_bp.route('/api/print_file', methods=['POST'])
|
||||
|
||||
@login_required
|
||||
def api_print_file():
|
||||
path = request.json.get('path')
|
||||
location = request.json.get('origin', 'local')
|
||||
client = get_octo_client()
|
||||
if client and path:
|
||||
allowed, err_msg = check_printer_control_permission(client)
|
||||
if not allowed:
|
||||
return jsonify({"success": False, "error": err_msg})
|
||||
try:
|
||||
client.select_file(location, path, print_after_select=True)
|
||||
return jsonify({"success": True})
|
||||
@@ -117,14 +174,34 @@ def api_print_file():
|
||||
return jsonify({"success": False, "error": "Not configured or missing path"})
|
||||
|
||||
@printer_bp.route('/control')
|
||||
@login_required
|
||||
def control():
|
||||
client = get_octo_client()
|
||||
webcam_url = None
|
||||
error = None
|
||||
if client:
|
||||
try:
|
||||
webcam_url = client.get_webcam_stream_url()
|
||||
raw_url = client.get_webcam_stream_url()
|
||||
# If it's an absolute url pointing to the base url, strip it or proxy it via octo_proxy
|
||||
from urllib.parse import urlparse, urlencode
|
||||
parsed_raw = urlparse(raw_url)
|
||||
base_config = SystemConfig.query.filter_by(key='octoprint_url').first()
|
||||
if base_config and base_config.value:
|
||||
base_url = base_config.value.rstrip('/')
|
||||
parsed_base = urlparse(base_url)
|
||||
# If they share the same host, replace with proxy
|
||||
# Usually OctoPrint webcam streams are on the same host or relative
|
||||
path = parsed_raw.path
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
query = parsed_raw.query
|
||||
|
||||
# build proxy url
|
||||
if query:
|
||||
webcam_url = url_for('printer.octo_proxy', path=path) + '?' + query
|
||||
else:
|
||||
webcam_url = url_for('printer.octo_proxy', path=path)
|
||||
else:
|
||||
webcam_url = raw_url
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
else:
|
||||
@@ -137,6 +214,9 @@ def api_command():
|
||||
cmd = request.json.get('command')
|
||||
client = get_octo_client()
|
||||
if client and cmd:
|
||||
allowed, err_msg = check_printer_control_permission(client)
|
||||
if not allowed:
|
||||
return jsonify({"success": False, "error": err_msg})
|
||||
try:
|
||||
if cmd == 'home':
|
||||
client.home_axes()
|
||||
@@ -246,7 +326,7 @@ def octo_embed():
|
||||
@printer_bp.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
||||
@login_required
|
||||
def octo_proxy(path):
|
||||
if not current_user.is_admin:
|
||||
if current_user.is_guest:
|
||||
return "Unauthorized", 403
|
||||
|
||||
url_config = SystemConfig.query.filter_by(key='octoprint_url').first()
|
||||
|
||||
Reference in New Issue
Block a user