import logging from flask import Flask, request, jsonify, Response, stream_with_context from flask_cors import CORS from dotenv import load_dotenv import os import subprocess import requests app = Flask(__name__) load_dotenv('/home/gds/printer/.env') # .env 파일 경로 CORS(app, resources={r"/*": {"origins": "*"}}) # 모든 출처에 대해 CORS를 활성화합니다. # 로그 디렉토리와 파일 설정 log_dir = '/home/gds/printer/log' if not os.path.exists(log_dir): os.makedirs(log_dir) log_file = os.path.join(log_dir, 'app.log') # 로깅 설정 logging.basicConfig(filename=log_file, level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s') app.logger.addHandler(logging.StreamHandler()) app.logger.setLevel(logging.DEBUG) def slice_stl(file_path, output_dir, options=None): """ STL 파일을 PrusaSlicer를 사용하여 슬라이스하는 함수 """ # PrusaSlicer 명령어 생성 prusa_slicer_path = '/usr/bin/PrusaSlicer' command = [ prusa_slicer_path, '-g', '--filament-type', options.get('filament_type'), '--idle-temperature', options.get('idle_temperature'), '--first-layer-temperature', options.get('first_layer_temperature'), '--temperature', options.get('temperature'), '--first-layer-bed-temperature', options.get('first_layer_bed_temperature'), '--bed-temperature', options.get('bed_temperature'), '--fill-density', options.get('fill_density')+"%", '--fill-pattern', options.get('fill_pattern'), '--infill-anchor', options.get('infill_anchor'), '--infill-anchor-max', options.get('infill_anchor_max'), '--top-fill-pattern', options.get('top_fill_pattern'), '--bottom-fill-pattern', options.get('bottom_fill_pattern'), '--skirts', options.get('skirts'), '--skirt-distance', options.get('skirt_distance'), '--skirt-height', options.get('skirt_height'), '--draft-shield', options.get('draft_shield'), '--min-skirt-length', options.get('min_skirt_length'), '--brim-type', options.get('brim_type'), '--brim-width', options.get('brim_width'), '--brim-separation', options.get('brim_separation'), '--support-material', options.get('support_material'), '--support-material-auto', options.get('support_material_auto'), '--support-material-threshold', options.get('support_material_threshold'), '--support-material-extrusion-width', options.get('support_material_extrusion_width'), '--raft-first-layer-density', options.get('raft_first_layer_density'), '--raft-first-layer-expansion', options.get('raft_first_layer_expansion'), '--perimeter-speed', options.get('perimeter_speed'), '--small-perimeter-speed', options.get('small_perimeter_speed'), '--external-perimeter-speed', options.get('external_perimeter_speed'), '--infill-speed', options.get('infill_speed'), '--solid-infill-speed', options.get('solid_infill_speed'), '--top-solid-infill-speed', options.get('top_solid_infill_speed'), '--support-material-speed', options.get('support_material_speed'), '--support-material-interface-speed', options.get('support_material_interface_speed'), '--bridge-speed', options.get('bridge_speed'), '--gap-fill-speed', options.get('gap_fill_speed'), '--travel-speed', options.get('travel_speed'), '--first-layer-speed', options.get('first_layer_speed'), '--first-layer-speed-over-raft', options.get('first_layer_speed_over_raft'), f'"{file_path}"' ] # 출력 디렉토리는 PrusaSlicer가 인식할 수 있는 형식으로 설정 command.extend(['-o', output_dir]) # skirts가 0일 때 관련 옵션 제거 if options.get('skirts') == '0': command = [opt for idx, opt in enumerate(command) if 'skirt' not in opt and (idx == 0 or 'skirt' not in command[idx - 1])] command = [opt for idx, opt in enumerate(command) if 'skirts' not in opt and (idx == 0 or 'skirts' not in command[idx - 1])] # brim-type이 no_brim일 때 관련 옵션 제거 if options.get('brim_type') == 'no_brim': command = [opt for idx, opt in enumerate(command) if 'brim' not in opt and (idx == 0 or 'brim' not in command[idx - 1])] command = [opt for idx, opt in enumerate(command) if 'draft' not in opt and (idx == 0 or 'draft' not in command[idx - 1])] # 이 코드 처리 중에 --support-material 이나 --support-material-auto가 true,false에 따라서 다르게 동작해야 되는데, 둘중 하나 통과하는 부분에서 # command option이 사라진것 같아 확인 필요 i = 0 while i < len(command): if command[i] == '--support-material' and options.get('support_material') == 'true': command[i + 1] = '' # Set the next index to empty string i += 1 # Move to the next index to skip removing '--support-material' continue if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'true': command[i + 1] = '' # Set the next index to empty string i += 1 # Move to the next index to skip removing '--support-material-auto' continue if command[i] == '--support-material' and options.get('support_material') == 'false': command.pop(i) # Remove '--support-material' command.pop(i) # Remove the value after '--support-material' continue if command[i] == '--support-material-auto' and options.get('support_material_auto') == 'false': command.pop(i) # Remove '--support-material-auto' command.pop(i) # Remove the value after '--support-material-auto' continue i += 1 app.logger.debug("command : %s", command) try: result = subprocess.run(' '.join(command), shell=True, capture_output=True, text=True, check=True) app.logger.debug("PrusaSlicer output: %s", result.stdout) return True except subprocess.CalledProcessError as e: app.logger.error("PrusaSlicer error: %s", e.stderr) return False @app.route('/slice', methods=['POST']) def slice_model(): if 'file' not in request.files: app.logger.error('No file part') return jsonify({'error': 'No file part'}) file = request.files['file'] if file.filename == '': app.logger.error('No selected file') return jsonify({'error': 'No selected file'}) # STL 파일을 저장할 경로 설정 upload_dir = '/home/gds/printer/stl' if not os.path.exists(upload_dir): os.makedirs(upload_dir) file_path = os.path.join(upload_dir, file.filename) file.save(file_path) app.logger.debug('File saved to %s', file_path) options = { 'filament_type': request.form.get('filament_type'), 'idle_temperature': request.form.get('idle_temperature'), 'first_layer_temperature': request.form.get('first_layer_temperature'), 'temperature': request.form.get('temperature'), 'first_layer_bed_temperature': request.form.get('first_layer_bed_temperature'), 'bed_temperature': request.form.get('bed_temperature'), 'fill_density': request.form.get('fill_density'), 'fill_pattern': request.form.get('fill_pattern'), 'infill_anchor': request.form.get('infill_anchor'), 'infill_anchor_max': request.form.get('infill_anchor_max'), 'top_fill_pattern': request.form.get('top_fill_pattern'), 'bottom_fill_pattern': request.form.get('bottom_fill_pattern'), 'skirts': request.form.get('skirts'), 'skirt_distance': request.form.get('skirt_distance'), 'skirt_height': request.form.get('skirt_height'), 'draft_shield': request.form.get('draft_shield'), 'min_skirt_length': request.form.get('min_skirt_length'), 'brim_type': request.form.get('brim_type'), 'brim_width': request.form.get('brim_width'), 'brim_separation': request.form.get('brim_separation'), 'support_material': request.form.get('support_material'), 'support_material_auto': request.form.get('support_material_auto'), 'support_material_threshold': request.form.get('support_material_threshold'), 'support_material_extrusion_width': request.form.get('support_material_extrusion_width'), 'raft_first_layer_density': request.form.get('raft_first_layer_density'), 'raft_first_layer_expansion': request.form.get('raft_first_layer_expansion'), 'perimeter_speed': request.form.get('perimeter_speed'), 'small_perimeter_speed': request.form.get('small_perimeter_speed'), 'external_perimeter_speed': request.form.get('external_perimeter_speed'), 'infill_speed': request.form.get('infill_speed'), 'solid_infill_speed': request.form.get('solid_infill_speed'), 'top_solid_infill_speed': request.form.get('top_solid_infill_speed'), 'support_material_speed': request.form.get('support_material_speed'), 'support_material_interface_speed': request.form.get('support_material_interface_speed'), 'bridge_speed': request.form.get('bridge_speed'), 'gap_fill_speed': request.form.get('gap_fill_speed'), 'travel_speed': request.form.get('travel_speed'), 'first_layer_speed': request.form.get('first_layer_speed'), 'first_layer_speed_over_raft': request.form.get('first_layer_speed_over_raft') } # PrusaSlicer를 사용하여 STL 파일 슬라이스 output_dir = '/home/gds/printer/gcode/' if not os.path.exists(output_dir): os.makedirs(output_dir) success = slice_stl(file_path, output_dir, options) if success: # G-code 파일 경로 gcode_file_path = os.path.join(output_dir, os.path.splitext(file.filename)[0] + '.gcode') return jsonify({'gcode_file_path': gcode_file_path}) else: return jsonify({'error': 'Failed to slice the model'}) @app.route('/getGCodeFile', methods=['GET']) def get_gcode_file(): gcode_file_path = request.args.get('gcodeFilePath') if not gcode_file_path: return jsonify({'error': 'Missing gcode_file_path parameter'}) try: with open(gcode_file_path, 'r') as file: gcode_string = file.read() return gcode_string except FileNotFoundError: return jsonify({'error': 'File not found'}) @app.route('/getPrusaConfig', methods=['GET']) def get_prusa_config(): config = { 'printer1': { 'url': os.getenv('PRUSA_PRINTER1_API_URL'), 'api_key': os.getenv('PRUSA_PRINTER1_API_KEY') }, 'printer2': { 'url': os.getenv('PRUSA_PRINTER2_API_URL'), 'api_key': os.getenv('PRUSA_PRINTER2_API_KEY') } } return jsonify(config) @app.route('/proxy', methods=['GET', 'POST', 'PUT', 'DELETE']) def proxy_prusa_link(): try: # 요청 URL과 파라미터 설정 url = request.args.get('url') if not url: return Response("URL is required", status=400) # PrusaLink에 보낼 요청 설정 headers = {key: value for key, value in request.headers.items() if key != 'Host'} response = requests.request( method=request.method, url=url, headers=headers, data=request.get_data(), cookies=request.cookies, stream=True # 스트리밍 응답 처리 ) # PrusaLink에서 받은 스트리밍 응답을 클라이언트에 전달 def generate(): for chunk in response.iter_content(chunk_size=8192): yield chunk proxy_response = Response(stream_with_context(generate()), status=response.status_code) for key, value in response.headers.items(): if key.lower() != 'content-encoding' and key.lower() != 'transfer-encoding': proxy_response.headers[key] = value return proxy_response except Exception as e: return Response(f"An error occurred: {str(e)}", status=500) if __name__ == '__main__': app.run(host='0.0.0.0', port=3000, debug=True)