"""팔레타이징 앱 진입점. Flask(백엔드) + pywebview(데스크톱 창) + waitress(WSGI 서버) 구성. 로봇 제어 로직과 HTTP API 는 전부 api_routes.py 에 있다. PyInstaller 로 빌드 시(frozen) 템플릿 경로가 임시 폴더(_MEIPASS)로 바뀌는 것을 처리한다. """ from flask import Flask, render_template, request, jsonify import sys import os from api_routes import api_routes import webview from waitress import serve # PyInstaller 실행파일(frozen)이면 압축 해제된 임시 폴더에서 템플릿을 찾는다 if getattr(sys, 'frozen', False): template_folder = os.path.join(sys._MEIPASS, 'templates') app = Flask(__name__, template_folder=template_folder, static_url_path='/static') else: app = Flask(__name__, static_url_path='/static') app.register_blueprint(api_routes) # 로봇 제어 API 라우트 등록 # ── 화면 라우트 ────────────────────────────────────────────── @app.route('/') def index(): """메인 화면: 팔레트 선택/적재 배치 설정.""" return render_template('layer.html') @app.route('/progress') def progess(): """일반 적재 진행 현황 화면.""" return render_template('progress.html') @app.route('/oneLineProgress') def oneLineProgess(): """한줄쌓기 진행 현황 화면.""" return render_template('oneLineProgress.html') # ── 종료 처리 ──────────────────────────────────────────────── def shutdown_waitress_server(): """waitress 서버 및 프로세스 전체 종료.""" print("Shutting down the server...") os._exit(0) def destroy(window): """pywebview 창 닫기.""" print('Destroying window..') window.destroy() print('Destroyed!') @app.route('/shutdown', methods=['POST']) def shutdown(): """UI 종료 버튼: 창 닫고 서버/프로세스 종료.""" destroy(test) shutdown_waitress_server() return 'Server shutting down' port = 5000 # pywebview 가 Flask 앱을 감싸는 데스크톱 창 생성 test = webview.create_window('Palletizing', app) if __name__ == "__main__": # webview.start()가 창이 닫힐 때까지 블록되고, # 창이 닫힌 뒤에는 waitress 단독 서버로 계속 서비스한다(0.0.0.0:5000). webview.start() serve(app, host="0.0.0.0", port=port)