"""白底图转 3D —— Blender 插件。

在 Blender 侧栏新增面板：选择一张白底图，点击“生成并导入”，
插件会把图片发给本地后端（默认 http://localhost:8000）生成 GLB 并直接导入当前场景。

前置：先启动后端 `python app.py`（默认 8000 端口）。
安装：编辑 → 偏好设置 → 插件 → 安装，选择本文件，勾启用。
"""

bl_info = {
    "name": "白底图转 3D (WhiteBG to 3D)",
    "author": "whitebg3d",
    "version": (1, 0, 0),
    "blender": (3, 0, 0),
    "category": "Import-Export",
}

import bpy
import json
import os
import tempfile
import urllib.request
import uuid
from io import BytesIO

from bpy.props import (
    BoolProperty,
    EnumProperty,
    FloatProperty,
    IntProperty,
    StringProperty,
)
from bpy.types import Operator, Panel, PropertyGroup


class WhiteBg3DProps(PropertyGroup):
    filepath: StringProperty(name="白底图", subtype="FILE_PATH", description="选择一张白底图")
    backend: StringProperty(name="后端地址", default="http://localhost:8000")
    mode: EnumProperty(
        name="模式",
        items=[
            ("contour", "轮廓挤出", "实心硬币/浮雕"),
            ("luminance", "亮度浮雕", "按明暗起伏"),
        ],
        default="contour",
    )
    use_rembg: BoolProperty(name="rembg 精抠", default=False)
    resolution: IntProperty(name="分辨率", default=128, min=32, max=256, step=8)
    height: FloatProperty(name="高度比例", default=0.3, min=0.05, max=1.0, step=0.05)
    bevel: IntProperty(name="边缘倒角", default=4, min=0, max=16)


def multipart_post(url, params, file_path, file_field="file"):
    boundary = "----wb3d" + uuid.uuid4().hex
    body = BytesIO()
    for k, v in params.items():
        body.write(f"--{boundary}\r\n".encode())
        body.write(f'Content-Disposition: form-data; name="{k}"\r\n\r\n'.encode())
        body.write(str(v).encode())
        body.write(b"\r\n")
    fn = os.path.basename(file_path)
    body.write(f"--{boundary}\r\n".encode())
    body.write(
        f'Content-Disposition: form-data; name="{file_field}"; filename="{fn}"\r\n'.encode()
    )
    body.write(b"Content-Type: image/png\r\n\r\n")
    with open(file_path, "rb") as f:
        body.write(f.read())
    body.write(b"\r\n")
    body.write(f"--{boundary}--\r\n".encode())
    data = body.getvalue()
    req = urllib.request.Request(url, data=data, method="POST")
    req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
    with urllib.request.urlopen(req, timeout=180) as resp:
        return resp.read().decode("utf-8")


class WHITEBG3D_OT_generate(Operator):
    bl_idname = "whitebg3d.generate"
    bl_label = "生成并导入 3D"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        props = context.scene.whitebg3d
        img = bpy.path.abspath(props.filepath)
        if not img or not os.path.isfile(img):
            self.report({"ERROR"}, "请先选择一张白底图。")
            return {"CANCELLED"}

        base = props.backend.rstrip("/")
        try:
            raw = multipart_post(
                base + "/api/process",
                {
                    "mode": props.mode,
                    "use_rembg": "true" if props.use_rembg else "false",
                    "resolution": props.resolution,
                    "height": props.height,
                    "bevel": props.bevel,
                },
                img,
            )
            info = json.loads(raw)
        except Exception as exc:  # noqa: BLE001
            self.report({"ERROR"}, f"调用后端失败：{exc}")
            return {"CANCELLED"}

        glb_url = info.get("glb_url")
        if not glb_url:
            self.report({"ERROR"}, "后端未返回模型。")
            return {"CANCELLED"}
        if glb_url.startswith("/"):
            glb_url = base + glb_url

        try:
            with urllib.request.urlopen(glb_url, timeout=120) as resp:
                data = resp.read()
            tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".glb")
            tmp.write(data)
            tmp.close()
            bpy.ops.import_scene.gltf(filepath=tmp.name)
            os.unlink(tmp.name)
        except Exception as exc:  # noqa: BLE001
            self.report({"ERROR"}, f"下载/导入 GLB 失败：{exc}")
            return {"CANCELLED"}

        self.report(
            {"INFO"},
            f"已导入（顶点 {info['stats']['vertices']}，面 {info['stats']['faces']}）",
        )
        return {"FINISHED"}


class WHITEBG3D_PT_panel(Panel):
    bl_label = "白底图转 3D"
    bl_idname = "VIEW3D_PT_whitebg3d"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "白底图3D"

    def draw(self, context):
        p = context.scene.whitebg3d
        layout = self.layout
        layout.prop(p, "filepath")
        layout.prop(p, "backend")
        layout.prop(p, "mode")
        layout.prop(p, "use_rembg")
        layout.prop(p, "resolution")
        layout.prop(p, "height")
        layout.prop(p, "bevel")
        layout.operator("whitebg3d.generate")


classes = (
    WhiteBg3DProps,
    WHITEBG3D_OT_generate,
    WHITEBG3D_PT_panel,
)


def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.Scene.whitebg3d = bpy.props.PointerProperty(type=WhiteBg3DProps)


def unregister():
    for c in reversed(classes):
        bpy.utils.unregister_class(c)
    del bpy.types.Scene.whitebg3d


if __name__ == "__main__":
    register()
