Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ dist/
# Generated at build time by scripts/download-python-embed.js
resources/python-embed/
resources/get-pip.py
builds/

# Local setup/build artifacts
api/python_setup.json
api/texture_baker/build/
api/uv_unwrapper/build/
api/texture_baker/texture_baker/_C*.so
api/uv_unwrapper/uv_unwrapper/_C*.so

# Python
api/__pycache__/
Expand Down Expand Up @@ -32,4 +40,3 @@ Thumbs.db
# Env
.env
.env.local

94 changes: 94 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Changelog

## v0.1.4-dev (2026-03-22)

### AMD GPU (ROCm) Support

- **Switched PyTorch index to ROCm 6.4** for AMD GPU support (RDNA 4 / gfx1201 tested on RX 9070 XT)
- **Replaced `onnxruntime-gpu` with `onnxruntime`** in requirements.txt — the GPU variant is NVIDIA-only; CPU ONNX runtime works fine for `rembg` background removal
- **GPU auto-detection at install time** (`electron/main/python-setup.ts`): checks `nvidia-smi` (NVIDIA), `rocminfo` (AMD ROCm), or `wmic` (Windows fallback), and passes the correct `--index-url` to pip:
- NVIDIA: `https://download.pytorch.org/whl/cu128`
- AMD: `https://download.pytorch.org/whl/rocm6.4`
- No GPU: `https://download.pytorch.org/whl/cpu`
- **Removed hardcoded `--index-url` from `api/requirements.txt`** — the URL is now injected by `python-setup.ts` at runtime based on detected GPU

### Dev Mode Setup (First-Run Install)

- **Dev mode no longer skips setup** (`electron/main/ipc-handlers.ts`): previously `setup:check` returned `{ needed: false }` when `!app.isPackaged`, meaning the venv and pip install never ran. Now it checks for the existence of `api/.venv/bin/python` (Unix) or `api/.venv/Scripts/python.exe` (Windows)
- **Dev mode creates venv at `api/.venv`** (`electron/main/python-setup.ts`): matches where `resolvePythonExecutable()` looks for it in dev mode
- **Windows dev mode support** (`electron/main/python-setup.ts`): added a new branch in `runFullSetup()` for `win32 && !app.isPackaged` that uses `findSystemPython()` and creates a venv with `Scripts/python.exe`
- **`findSystemPython()` prefers Python 3.12** over 3.14+: the candidate list (`python3.12`, `python3.11`, `python3.10`, `python3`, `python`) ensures PyTorch-compatible Python is selected even on systems where `python3` points to 3.14+

### C++ Extension Compilation

- **Automatic compilation of `texture_baker` and `uv_unwrapper`** during first-run setup (`electron/main/python-setup.ts`): added `buildCppExtensions()` that runs `setup.py build_ext --inplace` for both extensions after pip install completes. Failures are non-fatal (texture features disabled but app still works)
- **Added "Compiling extensions" step** to the setup UI (`src/areas/setup/FirstRunSetup.tsx`)

### Linux Packaged Build (AppImage)

- **Bundled Python 3.11.9 in Linux AppImage** (`package.json`): added `extraResources` for `python-embed` to the `linux` build config — previously only the `win` config included it, so the AppImage had no Python and setup would fail with ENOENT
- **Fixed `getEmbeddedPythonExe()` symlink handling** (`electron/main/python-setup.ts`): python-build-standalone uses symlinks (`python3` -> `python3.11`) which may not survive AppImage packaging. Now tries `bin/python3.11`, `bin/python3`, `bin/python` in order

### Setup UI Improvements

- **Added missing progress steps** to `FirstRunSetup.tsx`: "Finding Python", "Creating environment", and "Compiling extensions" now show in the setup progress indicator alongside the existing "Preparing Python", "Installing pip", and "Installing packages" steps

### Extension System

- **Allow downloading models for unverified extensions** (`src/areas/models/components/ExtensionCard.tsx`): previously the Install button was disabled for extensions without a signature in the official registry. Now any extension can download model weights (the "Unverified" badge still shows as a warning)
- **Added `GenerationCancelled` exception and `_check_cancelled()` method** to `BaseGenerator` (`api/services/generators/base.py`): required by newer extension versions that support cancellation during generation

### New Dependencies

Added to `api/requirements.txt`:
- `omegaconf>=2.3.0` — required by `hy3dshape` (Hunyuan3D 2.1 pipeline)
- `timm>=1.0.0` — required by `hy3dshape` (vision transformer for image encoding)
- `torchdiffeq>=0.2.5` — required by `hy3dshape` (ODE solver for flow matching)
- `pybind11>=2.12.0` — required to compile C++ extensions (`differentiable_renderer`)

### Custom Extensions Created

Created two custom extensions in `~/.config/Modly/extensions/` (user data, not in repo):

- **`hunyuan3d/`** — Full Hunyuan3D 2 model (3.3B params) with Standard/Turbo/Fast variants from `tencent/Hunyuan3D-2`. Uses `hy3dgen` pipeline, supports texture generation
- **`hunyuan3d-2.1/`** — Hunyuan3D 2.1 (latest) from `tencent/Hunyuan3D-2.1`. Uses `hy3dshape` pipeline, shape-only (PBR texture model requires ~21 GB VRAM)

### Build System

- **Linux AppImage** built and placed in `builds/linux/Modly-0.1.3.AppImage`
- **Windows NSIS installer** cross-compiled and placed in `builds/windows/Modly Setup 0.1.3.exe`

### Files Modified

| File | Changes |
|------|---------|
| `api/requirements.txt` | Removed hardcoded CUDA index URL, added ROCm-compatible deps, added omegaconf/timm/torchdiffeq/pybind11 |
| `electron/main/python-setup.ts` | GPU detection, Windows dev mode, C++ extension compilation, embedded Python symlink handling |
| `electron/main/ipc-handlers.ts` | Dev mode setup check for both Unix and Windows, dev mode setup:run passes correct userData path |
| `electron/main/python-bridge.ts` | No changes (already handled dev/packaged correctly) |
| `src/areas/setup/FirstRunSetup.tsx` | Added python/venv/extensions steps to progress UI |
| `src/areas/models/components/ExtensionCard.tsx` | Removed trusted-only gate on Install button |
| `api/services/generators/base.py` | Added `GenerationCancelled` exception class and `_check_cancelled()` method |
| `package.json` | Added `extraResources` for python-embed to Linux build config |

### Known Limitations

- **Hunyuan3D 2.1 texture generation** requires ~21 GB VRAM (PBR paint model) — not feasible on 16 GB cards. Shape-only generation works fine
- **Hunyuan3D Mini texture generation** requires the `hy3dgen` texgen C++ extensions (`custom_rasterizer`, `differentiable_renderer`) to be compiled from the downloaded model's `_hy3dgen/` source. These are NOT compiled automatically by the app — they must be built manually with `pip install --no-build-isolation .` from each extension directory. ROCm users also need `hipsparse`, `hipblaslt`, `hipsolver`, `hipcub`, and `rocthrust` system packages
- **Python 3.14 is not supported** by PyTorch — users on Arch Linux (or similar rolling-release distros) need Python 3.10-3.12 installed separately
- **Windows builds are unsigned** — users will see a SmartScreen warning on first run

### System Requirements Tested

- **OS**: Arch Linux (kernel 6.19.8)
- **GPU**: AMD Radeon RX 9070 XT (gfx1201, RDNA 4)
- **ROCm**: 7.2.0 with HIP runtime
- **Python**: 3.12.13 (system), 3.11.9 (bundled)
- **PyTorch**: 2.9.1+rocm6.4

---

## v0.1.3 (previous release)

- Fix: handle missing file path on drag and drop
- Fix: use requirements.txt hash to trigger reinstall
83 changes: 83 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# CLAUDE.md — Project Context for Claude Code

## Project Overview

Modly is an Electron + React + Python FastAPI desktop app for AI-powered image-to-3D mesh generation. It supports NVIDIA (CUDA), AMD (ROCm), and CPU-only setups.

## Architecture

- **Frontend**: Electron 33 + React 18 + TypeScript + Three.js (Vite build)
- **Backend**: Python FastAPI on port 8765, spawned by Electron as a child process
- **Extensions**: Model adapters live in `~/.config/Modly/extensions/` (each has `manifest.json` + `generator.py`)
- **Models**: Downloaded from HuggingFace to `~/.config/Modly/models/`

## Key Files

| File | Purpose |
|------|---------|
| `electron/main/python-setup.ts` | First-run setup: GPU detection, venv creation, pip install, C++ extension compilation |
| `electron/main/python-bridge.ts` | Spawns/manages the FastAPI Python process |
| `electron/main/ipc-handlers.ts` | IPC between renderer and main process, setup triggers |
| `api/requirements.txt` | Python dependencies (GPU-agnostic — index URL injected at runtime) |
| `api/services/generator_registry.py` | Discovers and loads extensions dynamically |
| `api/services/generators/base.py` | BaseGenerator ABC with GenerationCancelled, smooth_progress, _check_cancelled |
| `src/areas/setup/FirstRunSetup.tsx` | Setup progress UI |
| `src/areas/models/components/ExtensionCard.tsx` | Extension install UI |
| `package.json` | Build config — `extraResources` for both win and linux include python-embed |

## GPU Detection

`python-setup.ts` detects GPU at install time and passes `--index-url` to pip:
- NVIDIA (`nvidia-smi`): `cu128`
- AMD (`rocminfo`): `rocm6.4`
- None: `cpu`

The `requirements.txt` has NO `--index-url` — only `--extra-index-url https://pypi.org/simple`.

## Python Version

- Bundled: Python 3.11.9 (python-build-standalone for Linux, embeddable for Windows)
- Dev mode: Uses system Python — prefers 3.12 > 3.11 > 3.10 > 3.x (3.14+ not supported by PyTorch)
- `getEmbeddedPythonExe()` tries `python3.11`, `python3`, `python` in order (symlinks may not survive packaging)

## C++ Extensions

Two app-level extensions compiled during setup:
- `api/texture_baker/` — rasterizes barycentric coordinates for texture baking
- `api/uv_unwrapper/` — UV unwrapping for mesh texturing

The `hy3dgen` texture pipeline (used by Hunyuan3D Mini) has its own extensions (`custom_rasterizer`, `differentiable_renderer`) that must be compiled separately from the model's `_hy3dgen/` source directory using `pip install --no-build-isolation .`

AMD ROCm users need these system packages for C++ compilation: `hipsparse`, `hipblaslt`, `hipsolver`, `hipcub`, `rocthrust`.

## Build Commands

```bash
npm install # JS dependencies
npm run build # Build Electron app (electron-vite)
npm run preview # Launch in dev mode
npm run prepare-resources # Download bundled Python

# Release builds
npx electron-builder --linux AppImage
npx electron-builder --win nsis --x64 # Needs Windows python-embed in resources/
```

For Windows cross-compilation from Linux: swap `resources/python-embed` to the Windows embeddable package before building, swap back after.

## Extension Structure

```
~/.config/Modly/extensions/<extension-id>/
├── manifest.json # id, name, generator_class, models[], hf_repo, etc.
└── generator.py # Class extending BaseGenerator from services.generators.base
```

Extensions import from `services.generators.base` (BaseGenerator, smooth_progress, GenerationCancelled).

## Known Limitations

- Hunyuan3D 2.1 texture generation requires ~21 GB VRAM (shape-only works fine)
- Python 3.14+ not supported by PyTorch
- Windows builds are unsigned (SmartScreen warning)
- hy3dgen texgen C++ extensions are NOT auto-compiled by the app setup
12 changes: 7 additions & 5 deletions api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# PyTorch CUDA — index primaire pour torch/torchvision
# cu128 = CUDA 12.8 (driver ≥520, RTX 30xx/40xx/50xx)
# Changer cu128 → cu126 si le driver est plus ancien
--index-url https://download.pytorch.org/whl/cu128
# PyTorch index URL is set automatically at install time based on detected GPU.
# Do NOT add --index-url here — it is passed by python-setup.ts.
--extra-index-url https://pypi.org/simple

# Web server
Expand All @@ -21,13 +19,17 @@ pillow>=11.0.0
trimesh>=4.5.0
numpy>=1.26.0
rembg>=2.0.0 # background removal (SF3D + Hunyuan3D)
onnxruntime-gpu>=1.17.0 # runtime ONNX requis par rembg (GPU-accelerated)
onnxruntime>=1.17.0 # runtime ONNX requis par rembg (CPU — GPU variant is NVIDIA-only)
einops>=0.7.0 # tensor ops used by Hunyuan3D / hy3dshape pipeline
pymeshlab>=2023.12 # mesh processing required by hy3dshape postprocessors
xatlas>=0.0.8 # UV unwrapping for Hunyuan3D texture generation
pygltflib>=1.15.0 # GLB/GLTF I/O for Hunyuan3D texture pipeline

opencv-python-headless>=4.8.0 # required by hy3dgen.shapegen.preprocessors (cv2)
omegaconf>=2.3.0 # required by hy3dshape (Hunyuan3D 2.1)
timm>=1.0.0 # required by hy3dshape (vision transformer)
torchdiffeq>=0.2.5 # required by hy3dshape (ODE solver)
pybind11>=2.12.0 # required to compile C++ extensions (differentiable_renderer)

# Utils
aiofiles==24.1.0
Expand Down
10 changes: 10 additions & 0 deletions api/services/generators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from typing import Callable, Optional


class GenerationCancelled(Exception):
"""Raised when the user cancels a running generation."""
pass


def smooth_progress(
progress_cb: Callable[[int, str], None],
start: int,
Expand Down Expand Up @@ -142,6 +147,11 @@ def _auto_download(self) -> None:
# Helpers
# ------------------------------------------------------------------ #

def _check_cancelled(self, cancel_event: Optional[threading.Event]) -> None:
"""Raises GenerationCancelled if the cancel event is set."""
if cancel_event is not None and cancel_event.is_set():
raise GenerationCancelled("Generation was cancelled by the user.")

def _report(
self,
progress_cb: Optional[Callable[[int, str], None]],
Expand Down
14 changes: 11 additions & 3 deletions electron/main/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,23 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe
})
ipcMain.on('window:close', () => getWindow()?.close())

// Setup handlers — skipped in dev (uses .venv instead of python-embed)
// Setup handlers — in dev mode, check for api/.venv; in packaged mode, use userData venv
ipcMain.handle('setup:check', async () => {
if (!app.isPackaged) return { needed: false }
if (!app.isPackaged) {
const apiDir = join(app.getAppPath(), 'api')
const venvPython = process.platform === 'win32'
? join(apiDir, '.venv', 'Scripts', 'python.exe')
: join(apiDir, '.venv', 'bin', 'python')
return { needed: !existsSync(venvPython) }
}
const userData = app.getPath('userData')
return { needed: checkSetupNeeded(userData) }
})

ipcMain.handle('setup:run', async () => {
const userData = app.getPath('userData')
const userData = !app.isPackaged
? join(app.getAppPath(), 'api')
: app.getPath('userData')
const win = getWindow()
if (!win) return { success: false, error: 'No window available' }
try {
Expand Down
Loading