wangyiyi666/model-optimizer
1
1"""2模型优化服务 - Flask 前端3第一版:本地测试模式(先跑通完整通路)4 5后续部署到 HF Space 时可切换为 Gradio 版本6"""7from flask import Flask, request, render_template_string, send_file, jsonify8import os9import uuid10import shutil11from datetime import datetime12 13app = Flask(__name__)14 15# === 配置 ===16BASE_DIR = os.path.dirname(os.path.abspath(__file__))17INBOX = os.path.join(BASE_DIR, "..", "local", "tmp", "inbox")18OUTBOX = os.path.join(BASE_DIR, "..", "local", "tmp", "outbox")19os.makedirs(INBOX, exist_ok=True)20os.makedirs(OUTBOX, exist_ok=True)21 22SUPPORTED_FORMATS = [".glb", ".gltf", ".fbx", ".obj"]23 24def log(msg):25 timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")26 print(f"[{timestamp}] [WEB] {msg}")27 28 29HTML_TEMPLATE = """30<!DOCTYPE html>31<html>32<head>33 <title>3D 模型优化服务</title>34 <meta charset="utf-8">35 <style>36 body { font-family: -apple-system, sans-serif; max-width: 700px; margin: 50px auto; padding: 20px; background: #f5f5f5; }37 .card { background: white; border-radius: 12px; padding: 30px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }38 h1 { text-align: center; color: #333; }39 .upload-area { border: 2px dashed #ccc; border-radius: 8px; padding: 40px; text-align: center; margin: 20px 0; }40 .upload-area:hover { border-color: #667eea; }41 input[type="file"] { margin: 10px 0; }42 button { background: #667eea; color: white; border: none; padding: 12px 30px; border-radius: 6px; cursor: pointer; font-size: 16px; }43 button:hover { background: #5a67d8; }44 .result { padding: 15px; border-radius: 8px; margin: 15px 0; }45 .success { background: #d4edda; color: #155724; }46 .waiting { background: #fff3cd; color: #856404; }47 .error { background: #f8d7da; color: #721c24; }48 input[type="text"] { padding: 10px; width: 200px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; }49 .info { color: #666; font-size: 14px; }50 a.download { display: inline-block; background: #28a745; color: white; padding: 10px 20px; border-radius: 6px; text-decoration: none; margin-top: 10px; }51 </style>52</head>53<body>54 <h1>🛠️ 3D 模型优化服务</h1>55 56 <div class="card">57 <h2>📤 上传模型</h2>58 <form action="/upload" method="post" enctype="multipart/form-data">59 <div class="upload-area">60 <p>选择 3D 模型文件</p>61 <input type="file" name="model" accept=".glb,.gltf,.fbx,.obj">62 <p class="info">支持格式: GLB, GLTF, FBX, OBJ</p>63 </div>64 <button type="submit">🚀 提交优化</button>65 </form>66 {% if upload_msg %}67 <div class="result {{ upload_class }}">{{ upload_msg }}</div>68 {% endif %}69 </div>70 71 <div class="card">72 <h2>🔍 查询结果</h2>73 <form action="/status" method="get">74 <input type="text" name="task_id" placeholder="输入任务ID" value="{{ query_id or '' }}">75 <button type="submit">查询</button>76 </form>77 {% if status_msg %}78 <div class="result {{ status_class }}">{{ status_msg }}</div>79 {% endif %}80 {% if download_ready %}81 <a class="download" href="/download/{{ download_id }}">⬇️ 下载优化结果</a>82 {% endif %}83 </div>84 85 <div class="info" style="text-align:center; margin-top: 30px;">86 输出格式: GLB | Worker 每30秒检查一次任务87 </div>88</body>89</html>90"""91 92 93@app.route("/")94def index():95 return render_template_string(HTML_TEMPLATE)96 97 98@app.route("/upload", methods=["POST"])99def upload():100 file = request.files.get("model")101 if not file or file.filename == "":102 return render_template_string(HTML_TEMPLATE, 103 upload_msg="请选择文件", upload_class="error")104 105 filename = file.filename106 ext = os.path.splitext(filename)[1].lower()107 log(f"收到上传: {filename}")108 109 if ext not in SUPPORTED_FORMATS:110 log(f"格式不支持: {ext}")111 return render_template_string(HTML_TEMPLATE,112 upload_msg=f"不支持的格式: {ext}", upload_class="error")113 114 # 生成任务ID并保存文件115 task_id = str(uuid.uuid4())[:8]116 target_path = os.path.join(INBOX, f"{task_id}{ext}")117 file.save(target_path)118 file_size = os.path.getsize(target_path)119 120 log(f"文件已保存: {task_id}{ext} ({file_size} bytes), 任务ID: {task_id}")121 122 return render_template_string(HTML_TEMPLATE,123 upload_msg=f"✅ 上传成功!任务ID: {task_id} (请保存此ID用于查询结果)",124 upload_class="success")125 126 127@app.route("/status")128def status():129 task_id = request.args.get("task_id", "").strip()130 if not task_id:131 return render_template_string(HTML_TEMPLATE,132 status_msg="请输入任务ID", status_class="error", query_id=task_id)133 134 log(f"查询状态: {task_id}")135 136 # 检查 outbox137 result_file = os.path.join(OUTBOX, f"{task_id}_optimized.glb")138 if os.path.exists(result_file):139 size = os.path.getsize(result_file)140 log(f"任务 {task_id} 已完成 ({size} bytes)")141 return render_template_string(HTML_TEMPLATE,142 status_msg=f"✅ 优化完成!文件大小: {size} bytes",143 status_class="success", download_ready=True, 144 download_id=task_id, query_id=task_id)145 146 # 检查 inbox147 for f in os.listdir(INBOX):148 if f.startswith(task_id):149 log(f"任务 {task_id} 仍在队列中")150 return render_template_string(HTML_TEMPLATE,151 status_msg="⏳ 处理中,请稍后再查询...",152 status_class="waiting", query_id=task_id)153 154 log(f"任务 {task_id} 未找到")155 return render_template_string(HTML_TEMPLATE,156 status_msg="❌ 未找到该任务,请检查ID是否正确",157 status_class="error", query_id=task_id)158 159 160@app.route("/download/<task_id>")161def download(task_id):162 result_file = os.path.join(OUTBOX, f"{task_id}_optimized.glb")163 if os.path.exists(result_file):164 log(f"用户下载: {task_id}_optimized.glb")165 return send_file(result_file, as_attachment=True,166 download_name=f"{task_id}_optimized.glb")167 return "文件不存在", 404168 169 170if __name__ == "__main__":171 log("=" * 40)172 log("Web 前端启动")173 log(f" Inbox: {INBOX}")174 log(f" Outbox: {OUTBOX}")175 log("=" * 40)176 app.run(host="0.0.0.0", port=5000, debug=True)177 