modly-image-to-3d

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Modly Image-to-3D Skill

Modly 图像转3D技能

Skill by ara.so — Daily 2026 Skills collection.
Modly is a local, open-source desktop application (Windows/Linux) that converts photos into 3D mesh models using AI models running entirely on your GPU — no cloud, no API keys required.

该技能来自ara.so — 2026每日技能合集。
Modly是一款本地开源桌面应用(支持Windows/Linux),它依靠完全在本地GPU上运行的AI模型将照片转换为3D网格模型——无需云端服务,也不需要API密钥。

Architecture Overview

架构概述

modly/
├── src/                    # Electron + TypeScript frontend
│   ├── main/               # Electron main process
│   ├── renderer/           # React UI (renderer process)
│   └── preload/            # IPC bridge
├── api/                    # Python FastAPI backend
│   ├── generator.py        # Core generation logic
│   └── requirements.txt
├── resources/
│   └── icons/
├── launcher.bat            # Windows quick-start
├── launcher.sh             # Linux quick-start
└── package.json
The app runs as an Electron shell over a local Python FastAPI server. Extensions are GitHub repos with a
manifest.json
+
generator.py
that plug into the extension system.

modly/
├── src/                    # Electron + TypeScript 前端
│   ├── main/               # Electron 主进程
│   ├── renderer/           # React UI(渲染进程)
│   └── preload/            # IPC 桥接层
├── api/                    # Python FastAPI 后端
│   ├── generator.py        # 核心生成逻辑
│   └── requirements.txt
├── resources/
│   └── icons/
├── launcher.bat            # Windows 快速启动脚本
├── launcher.sh             # Linux 快速启动脚本
└── package.json
该应用以Electron外壳运行在本地Python FastAPI服务器之上。扩展是包含
manifest.json
generator.py
的GitHub仓库,可接入扩展系统。

Installation

安装步骤

Quick start (no build required)

快速启动(无需构建)

bash
undefined
bash
undefined

Windows

Windows

launcher.bat
launcher.bat

Linux

Linux

chmod +x launcher.sh ./launcher.sh
undefined
chmod +x launcher.sh ./launcher.sh
undefined

Development setup

开发环境搭建

bash
undefined
bash
undefined

1. Clone

1. 克隆仓库

2. Install JS dependencies

2. 安装JS依赖

npm install
npm install

3. Set up Python backend

3. 配置Python后端

cd api python -m venv .venv
cd api python -m venv .venv

Activate (Windows)

激活虚拟环境(Windows)

.venv\Scripts\activate
.venv\Scripts\activate

Activate (Linux/macOS)

激活虚拟环境(Linux/macOS)

source .venv/bin/activate
pip install -r requirements.txt cd ..
source .venv/bin/activate
pip install -r requirements.txt cd ..

4. Run dev mode (starts Electron + Python backend)

4. 启动开发模式(同时启动Electron和Python后端)

npm run dev
undefined
npm run dev
undefined

Production build

生产环境构建

bash
undefined
bash
undefined

Build installers for current platform

为当前平台构建安装包

npm run build
npm run build

Output goes to dist/

输出文件在dist/目录下


---

---

Key npm Scripts

关键npm脚本

bash
npm run dev        # Start app in development mode (hot reload)
npm run build      # Package app for distribution
npm run lint       # Run ESLint
npm run typecheck  # TypeScript type checking

bash
npm run dev        # 以开发模式启动应用(支持热重载)
npm run build      # 打包应用用于分发
npm run lint       # 运行ESLint检查
npm run typecheck  # TypeScript类型检查

Extension System

扩展系统

Extensions are GitHub repositories containing:
  • manifest.json
    — metadata and model variants
  • generator.py
    — generation logic implementing the Modly extension interface
扩展是包含以下文件的GitHub仓库:
  • manifest.json
    — 元数据和模型变体配置
  • generator.py
    — 实现Modly扩展接口的生成逻辑

manifest.json structure

manifest.json 结构

json
{
  "name": "My 3D Extension",
  "id": "my-extension-id",
  "description": "Generates 3D models using XYZ model",
  "version": "1.0.0",
  "author": "Your Name",
  "repository": "https://github.com/yourname/my-modly-extension",
  "variants": [
    {
      "id": "model-small",
      "name": "Small (faster)",
      "description": "Lighter variant for faster generation",
      "size_gb": 4.2,
      "vram_gb": 6,
      "files": [
        {
          "url": "https://huggingface.co/yourorg/yourmodel/resolve/main/weights.safetensors",
          "filename": "weights.safetensors",
          "sha256": "abc123..."
        }
      ]
    }
  ]
}
json
{
  "name": "My 3D Extension",
  "id": "my-extension-id",
  "description": "Generates 3D models using XYZ model",
  "version": "1.0.0",
  "author": "Your Name",
  "repository": "https://github.com/yourname/my-modly-extension",
  "variants": [
    {
      "id": "model-small",
      "name": "Small (faster)",
      "description": "Lighter variant for faster generation",
      "size_gb": 4.2,
      "vram_gb": 6,
      "files": [
        {
          "url": "https://huggingface.co/yourorg/yourmodel/resolve/main/weights.safetensors",
          "filename": "weights.safetensors",
          "sha256": "abc123..."
        }
      ]
    }
  ]
}

generator.py interface

generator.py 接口

python
undefined
python
undefined

api/extensions/<extension-id>/generator.py

api/extensions/<extension-id>/generator.py

Required interface every extension must implement

所有扩展必须实现的必填接口

import sys import json from pathlib import Path
def generate( image_path: str, output_path: str, variant_id: str, models_dir: str, **kwargs ) -> dict: """ Required entry point for all Modly extensions.
Args:
    image_path:  Path to input image file
    output_path: Path where output .glb/.obj should be saved
    variant_id:  Which model variant to use
    models_dir:  Directory where downloaded model weights live

Returns:
    dict with keys:
        success (bool)
        output_file (str) — path to generated mesh
        error (str, optional)
"""
try:
    # Load your model weights
    weights = Path(models_dir) / variant_id / "weights.safetensors"
    
    # Run your inference
    mesh = run_inference(str(weights), image_path)
    
    # Save output
    mesh.export(output_path)
    
    return {
        "success": True,
        "output_file": output_path
    }
except Exception as e:
    return {
        "success": False,
        "error": str(e)
    }
undefined
import sys import json from pathlib import Path
def generate( image_path: str, output_path: str, variant_id: str, models_dir: str, **kwargs ) -> dict: """ 所有Modly扩展的必填入口函数。
参数:
    image_path: 输入图像文件的路径
    output_path: 输出.glb/.obj文件的保存路径
    variant_id: 要使用的模型变体ID
    models_dir: 已下载模型权重的存储目录

返回:
    包含以下键的字典:
        success (bool) — 是否成功
        output_file (str) — 生成的网格文件路径
        error (str, 可选) — 错误信息
"""
try:
    # 加载模型权重
    weights = Path(models_dir) / variant_id / "weights.safetensors"
    
    # 运行推理
    mesh = run_inference(str(weights), image_path)
    
    # 保存输出结果
    mesh.export(output_path)
    
    return {
        "success": True,
        "output_file": output_path
    }
except Exception as e:
    return {
        "success": False,
        "error": str(e)
    }
undefined

Installing an extension (UI flow)

安装扩展(UI流程)

  1. Open Modly → go to Models page
  2. Click Install from GitHub
  3. Paste the HTTPS URL, e.g.
    https://github.com/lightningpixel/modly-hunyuan3d-mini-extension
  4. After install, click Download on the desired model variant
  5. Select the installed model and upload an image to generate
  1. 打开Modly → 进入模型页面
  2. 点击从GitHub安装
  3. 粘贴HTTPS链接,例如
    https://github.com/lightningpixel/modly-hunyuan3d-mini-extension
  4. 安装完成后,点击所需模型变体旁的下载
  5. 选择已安装的模型并上传图像即可开始生成

Official Extensions

官方扩展

ExtensionModel
modly-hunyuan3d-mini-extensionHunyuan3D 2 Mini

扩展模型
modly-hunyuan3d-mini-extensionHunyuan3D 2 Mini

Python Backend API (FastAPI)

Python后端API(FastAPI)

The backend runs locally. Key endpoints used by the Electron frontend:
python
undefined
后端在本地运行。Electron前端调用的关键接口:
python
undefined

Typical backend route patterns (api/main.py or similar)

典型后端路由示例(api/main.py或类似文件)

GET /extensions — list installed extensions

GET /extensions — 列出已安装的扩展

GET /extensions/{id} — get extension details + variants

GET /extensions/{id} — 获取扩展详情和变体信息

POST /extensions/install — install extension from GitHub URL

POST /extensions/install — 从GitHub URL安装扩展

POST /generate — trigger 3D generation

POST /generate — 触发3D模型生成

GET /generate/status — poll generation progress

GET /generate/status — 查询生成进度

GET /models — list downloaded model variants

GET /models — 列出已下载的模型变体

POST /models/download — download a model variant

POST /models/download — 下载模型变体

undefined
undefined

Calling the backend from Electron (IPC pattern)

从Electron调用后端(IPC模式)

typescript
// src/preload/index.ts — exposing backend calls to renderer
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('modly', {
  generate: (imagePath: string, extensionId: string, variantId: string) =>
    ipcRenderer.invoke('generate', { imagePath, extensionId, variantId }),

  installExtension: (repoUrl: string) =>
    ipcRenderer.invoke('install-extension', { repoUrl }),

  listExtensions: () =>
    ipcRenderer.invoke('list-extensions'),
})
typescript
// src/main/ipc-handlers.ts — main process handling
import { ipcMain } from 'electron'

ipcMain.handle('generate', async (_event, { imagePath, extensionId, variantId }) => {
  const response = await fetch('http://localhost:PORT/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ image_path: imagePath, extension_id: extensionId, variant_id: variantId }),
  })
  return response.json()
})
typescript
// src/renderer/components/GenerateButton.tsx — UI usage
declare global {
  interface Window {
    modly: {
      generate: (imagePath: string, extensionId: string, variantId: string) => Promise<{ success: boolean; output_file?: string; error?: string }>
      installExtension: (repoUrl: string) => Promise<{ success: boolean }>
      listExtensions: () => Promise<Extension[]>
    }
  }
}

async function handleGenerate(imagePath: string) {
  const result = await window.modly.generate(
    imagePath,
    'modly-hunyuan3d-mini-extension',
    'hunyuan3d-mini-turbo'
  )

  if (result.success) {
    console.log('Mesh saved to:', result.output_file)
  } else {
    console.error('Generation failed:', result.error)
  }
}

typescript
// src/preload/index.ts — 向后端暴露调用接口给渲染进程
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('modly', {
  generate: (imagePath: string, extensionId: string, variantId: string) =>
    ipcRenderer.invoke('generate', { imagePath, extensionId, variantId }),

  installExtension: (repoUrl: string) =>
    ipcRenderer.invoke('install-extension', { repoUrl }),

  listExtensions: () =>
    ipcRenderer.invoke('list-extensions'),
})
typescript
// src/main/ipc-handlers.ts — 主进程处理逻辑
import { ipcMain } from 'electron'

ipcMain.handle('generate', async (_event, { imagePath, extensionId, variantId }) => {
  const response = await fetch('http://localhost:PORT/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ image_path: imagePath, extension_id: extensionId, variant_id: variantId }),
  })
  return response.json()
})
typescript
// src/renderer/components/GenerateButton.tsx — UI层调用示例
declare global {
  interface Window {
    modly: {
      generate: (imagePath: string, extensionId: string, variantId: string) => Promise<{ success: boolean; output_file?: string; error?: string }>
      installExtension: (repoUrl: string) => Promise<{ success: boolean }>
      listExtensions: () => Promise<Extension[]>
    }
  }
}

async function handleGenerate(imagePath: string) {
  const result = await window.modly.generate(
    imagePath,
    'modly-hunyuan3d-mini-extension',
    'hunyuan3d-mini-turbo'
  )

  if (result.success) {
    console.log('网格模型已保存至:', result.output_file)
  } else {
    console.error('生成失败:', result.error)
  }
}

Writing a Custom Extension

编写自定义扩展

Minimal extension repository structure

最小化扩展仓库结构

my-modly-extension/
├── manifest.json
└── generator.py
my-modly-extension/
├── manifest.json
└── generator.py

Example: wrapping a HuggingFace diffusion model

示例:封装HuggingFace扩散模型

python
undefined
python
undefined

generator.py

generator.py

import torch from PIL import Image from pathlib import Path
def generate(image_path, output_path, variant_id, models_dir, **kwargs): device = "cuda" if torch.cuda.is_available() else "cpu" weights_dir = Path(models_dir) / variant_id
try:
    # Load model (example pattern)
    from your_model_lib import ImageTo3DPipeline
    
    pipe = ImageTo3DPipeline.from_pretrained(
        str(weights_dir),
        torch_dtype=torch.float16
    ).to(device)

    image = Image.open(image_path).convert("RGB")
    
    with torch.no_grad():
        mesh = pipe(image).mesh

    mesh.export(output_path)

    return {"success": True, "output_file": output_path}

except Exception as e:
    return {"success": False, "error": str(e)}

---
import torch from PIL import Image from pathlib import Path
def generate(image_path, output_path, variant_id, models_dir, **kwargs): device = "cuda" if torch.cuda.is_available() else "cpu" weights_dir = Path(models_dir) / variant_id
try:
    # 加载模型(示例代码)
    from your_model_lib import ImageTo3DPipeline
    
    pipe = ImageTo3DPipeline.from_pretrained(
        str(weights_dir),
        torch_dtype=torch.float16
    ).to(device)

    image = Image.open(image_path).convert("RGB")
    
    with torch.no_grad():
        mesh = pipe(image).mesh

    mesh.export(output_path)

    return {"success": True, "output_file": output_path}

except Exception as e:
    return {"success": False, "error": str(e)}

---

Configuration & Environment

配置与环境

Modly runs fully locally — no environment variables or API keys needed. GPU/CUDA is auto-detected by PyTorch in extensions.
Relevant configuration lives in:
package.json          # Electron app metadata, build targets
api/requirements.txt  # Python dependencies for backend
If you need to configure the backend port or extension directory, check the Electron main process config (typically
src/main/index.ts
) for constants like
API_PORT
or
EXTENSIONS_DIR
.

Modly完全在本地运行——无需环境变量或API密钥。扩展中的PyTorch会自动检测GPU/CUDA。
相关配置文件:
package.json          # Electron应用元数据、构建目标
api/requirements.txt  # 后端Python依赖
如果需要配置后端端口或扩展目录,请查看Electron主进程配置(通常是
src/main/index.ts
)中的常量,例如
API_PORT
EXTENSIONS_DIR

Common Patterns

常见模式

Check if CUDA is available in an extension

在扩展中检查CUDA是否可用

python
import torch

def get_device():
    if torch.cuda.is_available():
        print(f"Using GPU: {torch.cuda.get_device_name(0)}")
        return "cuda"
    print("No GPU found, falling back to CPU (slow)")
    return "cpu"
python
import torch

def get_device():
    if torch.cuda.is_available():
        print(f"使用GPU: {torch.cuda.get_device_name(0)}")
        return "cuda"
    print("未检测到GPU,回退至CPU(速度较慢)")
    return "cpu"

Progress reporting from generator.py

从generator.py上报进度

python
import sys
import json

def report_progress(percent: int, message: str):
    """Write progress to stdout so Modly can display it."""
    print(json.dumps({"progress": percent, "message": message}), flush=True)

def generate(image_path, output_path, variant_id, models_dir, **kwargs):
    report_progress(0, "Loading model...")
    # ... load model ...
    report_progress(30, "Processing image...")
    # ... inference ...
    report_progress(90, "Exporting mesh...")
    # ... export ...
    report_progress(100, "Done")
    return {"success": True, "output_file": output_path}
python
import sys
import json

def report_progress(percent: int, message: str):
    """将进度写入标准输出,以便Modly显示。"""
    print(json.dumps({"progress": percent, "message": message}), flush=True)

def generate(image_path, output_path, variant_id, models_dir, **kwargs):
    report_progress(0, "加载模型中...")
    # ... 加载模型 ...
    report_progress(30, "处理图像中...")
    # ... 推理计算 ...
    report_progress(90, "导出网格模型中...")
    # ... 导出结果 ...
    report_progress(100, "完成")
    return {"success": True, "output_file": output_path}

Adding a new page in the renderer (React)

在渲染进程中添加新页面(React)

typescript
// src/renderer/pages/MyPage.tsx
import React, { useEffect, useState } from 'react'

interface Extension {
  id: string
  name: string
  description: string
}

export default function MyPage() {
  const [extensions, setExtensions] = useState<Extension[]>([])

  useEffect(() => {
    window.modly.listExtensions().then(setExtensions)
  }, [])

  return (
    <div>
      <h1>Installed Extensions</h1>
      {extensions.map(ext => (
        <div key={ext.id}>
          <h2>{ext.name}</h2>
          <p>{ext.description}</p>
        </div>
      ))}
    </div>
  )
}

typescript
// src/renderer/pages/MyPage.tsx
import React, { useEffect, useState } from 'react'

interface Extension {
  id: string
  name: string
  description: string
}

export default function MyPage() {
  const [extensions, setExtensions] = useState<Extension[]>([])

  useEffect(() => {
    window.modly.listExtensions().then(setExtensions)
  }, [])

  return (
    <div>
      <h1>已安装扩展</h1>
      {extensions.map(ext => (
        <div key={ext.id}>
          <h2>{ext.name}</h2>
          <p>{ext.description}</p>
        </div>
      ))}
    </div>
  )
}

Troubleshooting

故障排除

ProblemFix
npm run dev
— Python backend not starting
Ensure venv is set up:
cd api && python -m venv .venv && pip install -r requirements.txt
CUDA out of memoryUse a smaller model variant or close other GPU processes
Extension install failsVerify the GitHub URL is HTTPS and the repo contains
manifest.json
at root
Generation hangsCheck that your GPU drivers and CUDA toolkit match the PyTorch version in
requirements.txt
App won't launch on LinuxMake
launcher.sh
executable:
chmod +x launcher.sh
Model download stallsCheck disk space; large models (4–10 GB) need adequate free space
torch
not found in extension
Ensure PyTorch is in
api/requirements.txt
, not just the extension's own deps
问题解决方法
npm run dev
— Python后端无法启动
确保虚拟环境已正确配置:
cd api && python -m venv .venv && pip install -r requirements.txt
CUDA内存不足使用更小的模型变体,或关闭其他占用GPU的进程
扩展安装失败验证GitHub URL是HTTPS格式,且仓库根目录包含
manifest.json
生成过程卡住检查GPU驱动和CUDA工具包版本是否与
requirements.txt
中的PyTorch版本匹配
Linux下应用无法启动确保
launcher.sh
可执行:
chmod +x launcher.sh
模型下载停滞检查磁盘空间;大型模型(4–10 GB)需要足够的可用空间
扩展中找不到
torch
确保PyTorch已添加到
api/requirements.txt
中,而不仅仅是扩展自身的依赖

Verifying GPU is detected

验证GPU是否被检测到

bash
cd api
source .venv/bin/activate   # or .venv\Scripts\activate on Windows
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'no GPU')"

bash
cd api
source .venv/bin/activate   # Windows系统使用.venv\Scripts\activate
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'no GPU')"

Resources

相关资源