Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

Image-to-3D3D 生成Electron

Modly:在自己 GPU 上把一张图变成 3D 网格的开源桌面应用

Modly 是 Lightning Pixel 开源的图生 3D 网格桌面应用(Windows / Linux / Apple Silicon macOS,v0.4.3,MIT 加署名条款),用你自己 GPU 上的开源模型把一张照片变成可用网格,推理全程不出机器。架构是 Electron 壳托管绑定 127.0.0.1:8765 的 Python FastAPI 后端;默认模型 TripoSR(约 2.4GB),Hunyuan3D 2 Mini、TripoSG、Trellis2 GGUF 等以「GitHub 仓库 + manifest.json」的扩展形式安装,manifest 里声明多源权重 model_sources、共享权重 weight_groups,以及带 size_gb / vram_gb 的可分别安装量化档 weight_variants。生成流程用节点图工作流表达(Image → Generate Mesh → Add to Scene),场景走 modly.scene-manifest.v1 契约,导出支持 glb/stl/obj/ply,并为 OrcaSlicer 单开一条带毫米缩放的 stl/obj 打印路径。对外还给了三条 agent 通路:stdlib-only 的 JSON-first CLI(仓库自带 SKILL.md)、暴露 9 个 modly_* 工具的 MCP server、以及可跑本地 llama.cpp GGUF 池或外部模型端点的应用内工具调用 agent。AMD ROCm 自动检测、Apple Silicon 内存预算串行工作流与 Jetson headless 运行都附有实测数据的文档。

lightningpixel/modly8.1kTypeScriptNOASSERTION3 min read

一句话定位

Modly 是 Lightning Pixel 开源的桌面应用,把「本地、开源、图生 3D 网格」三件事装进一个安装包:给一张照片(或一段提示词),用你自己 GPU 上跑的开源模型生成可直接使用的 3D 网格,推理全程不出机器。项目 2026 年 3 月建仓,当前版本 0.4.3,主语言 TypeScript,GitHub 星标约 8100、fork 约 760,官网 modly3d.app。平台覆盖 Windows、Linux 与 Apple Silicon macOS——Intel Mac、通用二进制与 Rosetta 回落被架构决策记录(ADR)明确划到范围外。

协议是 MIT 外加一条署名条款:二次分发必须在应用 UI 或文档里保留 Lightning Pixel 的可见署名。仓库既提供各平台安装包,也允许 clone 之后用 launch.bat / ./launch.sh 免安装直接跑。

进程模型:Electron 壳托管一个本地 FastAPI

Modly 不是「前端调云服务」,而是「桌面壳托管推理服务」:Electron 主进程负责启动并管理一个绑定 127.0.0.1:8765 的 Python FastAPI(uvicorn),前端是 React + @react-three/fiber / drei 的三维视图,节点图用 @xyflow/react,状态管理用 zustand,几何查询加速用 three-mesh-bvh,另外带 gaussian-splats-3d 处理高斯泼溅资产。api/main.py 里注册的路由把能力面摊得很清楚:status、settings、model、generate、optimize、extensions、export、workflow-runs、agent、llm,外加一个把 workspace 目录直接吐给前端的 /workspace/{full_path} 文件端点。

两处细节值得抄:一是启动时 ensure_utf8_stdio() 必须先于任何 print/日志执行,否则 Windows 上子进程管道会把 UTF-8 输出打碎;二是 CORS 里显式写了 expose_headers=["Content-Length"],代码注释给出原因是 drei 的 SplatLoader 要用这个响应头给缓冲定尺寸,而跨域 JS 看不到未被 expose 的头。这类「不写就静默坏掉」的约束,在桌面壳 + 本地 HTTP 的架构里格外多。

扩展系统:模型不是内置的,是从 GitHub 装来的

Modly 本体不自带大模型权重,也不替扩展安装 PyTorch。默认模型是 TripoSR(约 2.4GB),下载到 ~/.modly/models/TripoSR/;其余能力都以扩展形式存在——一个扩展就是一个含 manifest.json 的 GitHub 仓库,在 Models 页点 Install from GitHub、贴上 HTTPS URL 即可安装。官方扩展目前有五个:Hunyuan3D 2 Mini 及其 Turbo / Fast 变体、TripoSG、Trellis2 GGUF。

manifest 的几个字段解决的是真实分发里的脏活:model_sources 允许一个模型节点的权重分散在多个 Hugging Face 仓库(逐个校验、顺序下载,且不能与 weight_variants 同时使用);weight_groups 让同一扩展内的多个节点共享一份权重;weight_variants 提供可分别安装的量化档,每档自带 size_gb(下载体积)与 vram_gb(大致显存需求),用户在点下载之前就知道要花多少磁盘和多少显存;params_schema 声明节点参数与默认值。下载侧的可观测性同样是显式设计:字节级进度、.part 断点续传(对着重定向后的最终 URL 发 Range,上游跳 CDN 也能续)、以 manifest 声明的 download_check 判定安装完成,而不是「目录存在就算装好了」。

节点图工作流与场景契约

生成流程被抽象成节点图。官方给的入门图是 Image → Generate Mesh → Add to Scene,在 Workflows 页连好线,再到 Generate 页选中该工作流点 Generate 3D Model,问题都在 Settings/Logs/Errors 面板里看。图在执行前会跑一遍 preflight 校验:接线非法时保留当前视图、用内联警告与 toast 报错,而不是把三维视图整块替换成错误页——这条同样写在 Apple Silicon 那份 ADR 里,属于「长任务不能因为一次错接线就把用户已有成果清掉」的产品决定。

比节点图更硬的是场景契约:一个 scene 是 workspace 下含 scene-manifest.json(schema modly.scene-manifest.v1)的目录,不是任意 JSON 文件,要用 Load Scene 节点选择并校验。支持场景的生成器实现 generate_artifact(input_kind, artifact_path, ...),通用端点 POST /generate/from-artifact 目前只接受 scene 这一种 artifact,而且首版契约里 scene 只能作为模型节点唯一的 input(不能塞进 inputs),过程节点与混合输入的场景节点会被直接拒;老的 POST /generate/from-image 与既有图像生成器保持不变。网格优化(smooth / decimate)既能吃 workspace 相对路径的网格,也能吃导入的绝对路径网格,优化结果写回 workspace,因此在应用内可见、可复用。

三个 Agent 接口:CLI、MCP、应用内 agent

Modly 把「被 AI 驱动」当成一等公民,给了三条互不替代的通路。第一条是仓库自带的 tools/modly-cli/agent.py:一个只依赖标准库的 CLI,规范化命令是 health、model、workflow-run、capability、process-run,最终结果以机器可读 JSON 打到 stdout,进度 JSON 行走 stderr;友好的 generate 命令是 POST /workflow-runs/from-image 加轮询再加导出的包装,返回里带 status_command / cancel_command 这类恢复元数据,失败也是结构化的(例如 {"ok": false, "code": "API_UNAVAILABLE"})。仓库里还直接放了一份 SKILL.md,写明「桌面应用先启动,再调 CLI」的前置条件——它显然是为 Claude Code / Codex 这类技能体系准备的。兼容性面被刻意隔离:legacy 包旧的 /generate/* 作业端点,dev serve-api 只起 FastAPI 因而不能证明 Electron 桥就绪,experimental comfy-* 是外部 ComfyUI 编排助手而非规范契约。

第二条是 api/mcp_server.py,把 9 个 modly_* 工具暴露给任意 MCP 客户端:list_models、switch_model、generate_from_image、get_generation_status、decimate_mesh、smooth_mesh、import_mesh、unload_models、get_settings。第三条是应用内 agent:api/routers/agent.py 自带一套 9 个工具(list_models、unload_models、get_mesh_info、decimate_mesh、smooth_mesh、get_generation_status、list_workflows、run_workflow、create_workflow),能按需构造工作流图并执行工具调用,模型可以选本地 GGUF,也可以走带 API Key 的外部模型端点。

支撑应用内 agent 的是一个本地 LLM 引擎:api/services/llm_server.py 管理 llama.cpp 的 llama-server 子进程池(默认端口 8791,每个模型一个进程一个端口,并发数可配、auto 按显存定档),二进制按平台自动选(有 NVIDIA 驱动走 CUDA,否则 Vulkan,再否则 CPU),模型来自内置目录或用户自己丢进 ~/.modly/llm/models/ 的 .gguf。代码注释把取舍说得很直白:Modly 首先是个 3D 生成应用,所以闲置换 IDLE_TTL_SECONDS(默认 300 秒)后就把 LLM 从显存里逐出,绝不让它占着 3D 生成要用的显存不放;agent 跑完工作流后也会主动卸载本地 LLM。

导出:从 glTF 到切片软件

导出支持 glb / stl / obj / ply 四种格式,并且单独开了一条面向 3D 打印的路径:切片格式只认 stl 与 obj,代码注释写明原因是 OrcaSlicer 根本不能导入 glTF/GLB,所以 .glb 在这条路上被刻意排除。打印路径还会按 longest_mm 把模型缩放到真实毫米尺寸,并绕 X 轴转 90 度以匹配打印朝向。对一个「图生 3D」的应用来说,这段代码是把生成结果真正送到物理世界的那一公里。

平台工程:ROCm、Apple Silicon、Jetson

三份文档/ADR 暴露了这个项目最扎实的部分——它把「非 NVIDIA 桌面怎么办」当工程问题解,而不是留一句不支持。AMD 侧:检测集中在 electron/main/gpu-detect.ts,NVIDIA 保持优先(双卡机器行为不变),AMD 机器一律上报 gpu_sm = 0、cuda_version = 0,绝不合成一个假的 compute capability;扩展通过 torch_flavor 安装参数得知要装 ROCm 版 torch,而那些把 --index-url .../whl/cu124 写死、无法修改的第三方扩展,则由 electron/main/setup-launcher.ts 里的重写垫片拦下 pip 调用来纠正。ADR 特别解释了 gpu_sm = 0 是「承重」的:老扩展按这个数字选最保守分支,也因此在 onnxruntime-gpu(只有 CUDA)面前不会翻车;而 PyTorch 的 HIP 构建实现了完整的 torch.cuda API,get_device_capability() 会报出与 sm_120 Blackwell 无法区分的 (12, 0),所以问 GPU 的地方宁可用 nvidia-smi 也不用 torch。计算目标的发现方式按平台分家:Linux 读内核 KFD 拓扑里的 gfx_target_version(不需要装 ROCm、不需要外部二进制),Windows 只能拿 Win32_VideoController 的 PCI device id 查一张按芯片维护的表,表里没有的 AMD 卡回落 CPU 并给出可执行的提示,而不是猜一个 wheel。实测口径也写清楚了:Radeon RX 9060 XT(gfx1200)上 torch 2.13.0+rocm7.2 可加载,16GB 显存能分配并回读 14GB,一次完整图生 3D 走正常 ExtensionProcess 路径耗时 221 秒;Windows 只核对了 wheel URL、cp311 可用性与索引布局,没做端到端验证。顺带还修了一个 AppImage bug:ensureStableEmbeddedPython() 用 fs.cp 复制内置 Python,会把相对符号链接改写成指向临时挂载点 /tmp/.mount_Modly-XXXXXX/ 的绝对路径,于是所谓「稳定副本」并不稳定,下一次启动基于它建的扩展 venv 全死在一句误导性的 No module named 'PIL' 上。

Apple Silicon 侧的核心约束是统一内存:16GB 机器上两个重型 GPU 阶段同时驻留能把整机搞崩,所以工作流被设计成串行且带内存预算,一次只驻留一个重生成阶段,阶段之间靠文件交接;而 MPS 显存靠 Python 侧清理并不能可靠归还,「释放显存」的可靠边界就是终止持有它的子进程——Unix 上 Python 桥是自己的进程组组长,退出时整组杀掉,取消操作先发协作请求、宽限期后升级为强杀。Jetson 侧则是一份诚实的非官方指南:Modly 不官方支持 Jetson,但因为 FastAPI 后端完全独立、纯 HTTP 驱动,可以不起 Electron、不要显示器,在 Jetson AGX Orin(64GB、JetPack 6.2、sm_87)上 headless 跑后端再用 curl 驱动;文档逐条列出必须打的三个补丁——换成 jetson-ai-lab 的 Jetson 版 torch(钉死的 torch 2.5.1+cu124 SBSA wheel 没有 sm_87 kernel,能加载但每个 kernel 都报 no kernel image is available)、钉 numpy < 2、绕开在 Tegra CPU 上直接段错误的 rembg / ONNX Runtime。实测模型是 Hunyuan3D-2 Mini(仅网格、无贴图),属于约 6GB 显存档。

它适合谁,以及可以从它身上拿走什么

作为工具,Modly 的位置很明确:需要在本地批量把参考图变成网格资产、又不想把图片上传给任何云端服务的人——独立创作者、游戏与可视化管线的资产前置环节,以及需要离线可复现的研究环境。仓库里同时存在 api/texture_baker 与 api/uv_unwrapper(贴图烘焙与 UV 展开的原生模块),但 AMD 那份 ADR 明确说标准扩展安装流程不构建它们,所以 ROCm 路径下贴图生成不在覆盖范围内;Jetson 实测也是「仅网格」。换句话说,当前稳的是几何,贴图仍要看平台。

作为工程样本,它有三样东西值得直接借鉴:其一,把模型分发外包给「GitHub 仓库 + manifest.json」,本体不背权重也不背 torch 安装,扩展生态因此可以独立演进;其二,把 agent 接口做成规范契约(JSON-first CLI + MCP + 结构化错误码 + 随仓库分发的 SKILL.md),而不是只留一个 UI;其三,把非 NVIDIA 平台的适配写成有状态、有日期、有实测数据的 ADR,明确区分「Linux 已验证 / Windows 未验证」,这种诚实度在开源 AI 工具里并不常见。

相关开源项目

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。