Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
1038f12
fix(extensions): recover worker after failed texture setup
Souptik96 Aug 14, 2026
f9a828f
feat: add AMD ROCm support (Linux + Windows)
lionrayonnant Aug 17, 2026
494dabf
feat(models): support multiple Hugging Face sources per node
DrHepa Aug 21, 2026
3a6a04d
fix(agent): surface HTTP errors from the unload_models tool
kevin9327 Aug 30, 2026
da1aed5
fix(agent): free the LLM's VRAM on the normal path after a workflow runs
kevin9327 Aug 30, 2026
8339e9f
Merge upstream dev into feat/amd-rocm-support
claude Sep 1, 2026
3a6523a
Address review: torch-free detection, shim coverage, contract parity
claude Sep 1, 2026
d4d5fc3
Merge pull request #286 from kevin9327/fix/agent-unload-error
lightningpixel Sep 2, 2026
38f14c9
Merge pull request #289 from kevin9327/fix/agent-workflow-vram-unload
lightningpixel Sep 2, 2026
d968439
fix(workflow-runs): take part in the job TTL purge so the headless AP…
kevin9327 Aug 30, 2026
4f47b21
fix(generate): read WORKSPACE_DIR dynamically so relocated output isn…
kevin9327 Aug 30, 2026
efcef68
feat: notify user on generation errors and fix launch
iammojogo-sudo Sep 3, 2026
db45535
fix: correct isTextOnly check for multi-input models, rewrite ImagePr…
iammojogo-sudo Jun 24, 2026
f3320cf
feat: ImagePreviewNode supports direct ImageNode input without running
iammojogo-sudo Jun 24, 2026
9ebbfd5
fix: register imagePreviewNode as passthrough
lightningpixel Sep 4, 2026
271f81c
refactor: dedupe mimeFromPath, share workspace-url helper, clarify docs
lightningpixel Sep 4, 2026
f0029df
Merge upstream dev and harden multi-source model downloads
DrHepa Sep 4, 2026
343a753
fix(workflows): resolve ImagePreviewNode preview through passthrough …
lightningpixel Sep 4, 2026
6ddf921
Merge pull request #205 from iammojogo-sudo/fix/preview-image-data-url
lightningpixel Sep 4, 2026
799dfd4
Merge pull request #259 from Souptik96/fix/extension-worker-texture-s…
lightningpixel Sep 4, 2026
e010ea4
fix(model): make the missing-sources error reachable in hf-download-s…
lightningpixel Sep 4, 2026
36edec3
fix(model): preserve completed sources when cancelling a multi-source…
lightningpixel Sep 4, 2026
7f7e5b3
Merge pull request #275 from DrHepa/feat/273-multi-model-sources
lightningpixel Sep 4, 2026
3fa1c4f
docs: add CONTRIBUTING.md and /assign command bot
lightningpixel Sep 4, 2026
b960e0a
Merge pull request #326 from lightningpixel/docs/contributing-assign-…
lightningpixel Sep 4, 2026
732303e
feat(mesh): add unified mesh operations registry
DrHepa Sep 5, 2026
3f54f9e
feat: sync project board status with /assign and linked PRs
lightningpixel Sep 5, 2026
36fc937
Merge pull request #330 from lightningpixel/feat/board-status-automation
lightningpixel Sep 5, 2026
165c260
Merge pull request #267 from lionrayonnant/feat/amd-rocm-support
lightningpixel Sep 6, 2026
1a677e3
Merge pull request #287 from kevin9327/fix/generation-dynamic-workspa…
lightningpixel Sep 12, 2026
814dc8e
Merge pull request #288 from kevin9327/fix/workflow-runs-job-purge
lightningpixel Sep 12, 2026
3fbc43c
feat: add point light support with UI controls and 3D markers
iammojogo-sudo Jun 23, 2026
f748b81
feat: increase point light intensity slider max to 16
iammojogo-sudo Jun 24, 2026
9bdaa90
fix: clear gizmo mode on selection change; add rotate/scale gizmos fo…
iammojogo-sudo Sep 12, 2026
7f5a505
fix(process-runner): rebuild a cached runner when its workspace moves
kevin9327 Sep 12, 2026
2b56f86
fix(process-runner): settle a JS process run when its worker dies
kevin9327 Sep 12, 2026
f2f88bc
fix(mesh): validate registry params and map operation errors
DrHepa Sep 13, 2026
b350595
fix(mesh-ops): validate boolean and select params in generic operatio…
lightningpixel Sep 13, 2026
3cde702
Merge pull request #329 from DrHepa/feat/295-mesh-ops-registry
lightningpixel Sep 13, 2026
82c4605
fix(point-lights): wire panel selection, fix gizmo carry-over, add se…
lightningpixel Sep 13, 2026
10b9bd1
Merge pull request #229 from iammojogo-sudo/feat/light-updates
lightningpixel Sep 13, 2026
aca9a79
feat: add "Open in OrcaSlicer" export action
wenghaishi Sep 14, 2026
63b731c
feat(models): support extension-scoped shared weight groups
DrHepa Sep 14, 2026
0e97260
fix(models): harden shared weight lifecycle and runtime contracts
DrHepa Sep 14, 2026
2dd0ce6
feat(models): install and remove weight variants per node
Lorchie Sep 18, 2026
a0ade53
fix(slicer): slice imported meshes, and stop rotating and rescaling b…
lightningpixel Sep 19, 2026
db0a580
Merge pull request #347 from wenghaishi/feat/open-in-orcaslicer
lightningpixel Sep 19, 2026
3166012
fix(workflows): map audio slots of multi-input nodes to the file path
sckid1108 Sep 22, 2026
3f77304
fix(generate): accept image drops from Explorer
Yi-111-a Sep 25, 2026
6da39f9
fix(export,optimize): read WORKSPACE_DIR dynamically so edits work af…
kevin9327 Sep 12, 2026
809c02a
Merge pull request #360 from Yi-111-a/fix/image-explorer-drop
lightningpixel Oct 1, 2026
e816ce0
Merge pull request #359 from sckid1108/fix/multi-input-audio-slot
lightningpixel Oct 1, 2026
2320b0b
feat(workflows): support scene model artifacts
DrHepa Sep 21, 2026
10cdc15
fix(workflows): address scene artifact review
DrHepa Oct 1, 2026
e75f19b
Add files via upload
iammojogo-sudo Oct 2, 2026
937a173
Delete WorkflowPanel.tsx
iammojogo-sudo Oct 2, 2026
b1a8a9a
Enhance WorkflowPanel with FloatInput props and image handling
iammojogo-sudo Oct 2, 2026
8f1a220
Add files via upload
iammojogo-sudo Oct 2, 2026
f3a7014
fix: address review on float sliders, image drop and error notifications
lightningpixel Oct 2, 2026
efef79e
Merge branch 'dev' into feat/progressbar_value-manifest-ui
lightningpixel Oct 2, 2026
42f6d0a
chore: normalize ExtensionNode.tsx line endings to LF
lightningpixel Oct 2, 2026
131b2f5
Merge remote-tracking branch 'origin/dev' into feat/progressbar_value…
lightningpixel Oct 2, 2026
0fed890
Merge pull request #363 from iammojogo-sudo/feat/progressbar_value-ma…
lightningpixel Oct 2, 2026
fd3da97
fix(api): keep status responsive during model loads, share scene helpers
lightningpixel Oct 2, 2026
675e648
Merge remote-tracking branch 'origin/dev' into feat/354-scene-artifacts
lightningpixel Oct 2, 2026
39cc58a
Merge pull request #357 from DrHepa/feat/354-scene-artifacts
lightningpixel Oct 2, 2026
50783c9
Merge origin/dev into feat/343-extension-shared-weight-groups
lightningpixel Oct 2, 2026
72bf7b0
fix(models): allow weight removal without a backend, scope unloads
lightningpixel Oct 2, 2026
622783f
Merge pull request #348 from DrHepa/feat/343-extension-shared-weight-…
lightningpixel Oct 2, 2026
0c01cba
Merge origin/dev into feat/weight-variants
lightningpixel Oct 2, 2026
c177ca7
fix(registry): match registration capability under Windows short paths
lightningpixel Oct 2, 2026
69e1014
Merge pull request #353 from lightningpixel/feat/weight-variants
lightningpixel Oct 2, 2026
03f0616
Merge remote-tracking branch 'origin/dev' into fix/process-runner-wor…
lightningpixel Oct 2, 2026
b978eac
fix(process-runner): replace a worker that dies between runs
lightningpixel Oct 2, 2026
0669755
Merge pull request #342 from kevin9327/fix/process-runner-worker-exit
lightningpixel Oct 2, 2026
6bacc2f
fix(export,optimize): check workspace containment by ancestry
lightningpixel Oct 2, 2026
3344dcc
Merge pull request #340 from kevin9327/fix/export-optimize-dynamic-wo…
lightningpixel Oct 2, 2026
56f6e03
test(process-runner): stub electron when loading the runner outside E…
lightningpixel Oct 3, 2026
cab6bb9
feat(llm): llama.cpp engine pool, model library and cloud providers
Lorchie Oct 3, 2026
f504db0
Merge remote-tracking branch 'origin/dev' into feat/llamacpp-cloud-pr…
Lorchie Oct 3, 2026
7d35df4
Merge pull request #338 from kevin9327/fix/process-runner-stale-works…
lightningpixel Oct 3, 2026
9123d8b
chore(deps): add jsdom for the React hook tests
lightningpixel Oct 3, 2026
830f0b0
feat(agent): keep the local LLM engine and models in an agent data fo…
lightningpixel Oct 3, 2026
dccca69
feat(agent): engine status in settings and a model browser
lightningpixel Oct 3, 2026
5c62c72
feat(agent): redesign the Agent settings page and the model library
lightningpixel Oct 3, 2026
50ae1c4
fix(llm): verify the engine archive against its published sha256
lightningpixel Oct 3, 2026
b6f5260
fix(agent): keep code/CAD models out of the agent model selection
lightningpixel Oct 3, 2026
221e44e
Merge pull request #365 from lightningpixel/feat/llamacpp-cloud-provi…
lightningpixel Oct 3, 2026
cf8d2ad
dump version 0.4.3
lightningpixel Oct 4, 2026
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
40 changes: 40 additions & 0 deletions .github/workflows/model-weights.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Model weight regressions

on:
pull_request:
branches: [dev, main]
paths:
- 'api/**'
- 'electron/**'
- 'src/areas/models/**'
- 'src/shared/types/electron.d.ts'
- 'package*.json'
- '.github/workflows/model-weights.yml'

permissions:
contents: read

jobs:
model-weights:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python: ['3.11', '3.12']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: npm ci --ignore-scripts --no-audit --no-fund
- run: python -m pip install fastapi httpx
- name: Download, deletion, manifests and install queue
run: node --test electron/main/model-weight-ipc.test.mjs electron/main/model-sources.test.mjs electron/main/model-download-plan.test.mjs electron/main/model-download-preload.test.mjs electron/main/extension-install-utils.test.mjs src/areas/models/utils.test.mjs
- name: Python runtime and download regressions
working-directory: api
run: python -m unittest tests.test_model_sources tests.test_model_router tests.test_generator_registry tests.test_extension_process tests.test_runner
180 changes: 180 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ npm run build

## Platform notes

- AMD GPUs are supported through ROCm on Linux and Windows: a Radeon card is detected
automatically and extensions are steered to ROCm PyTorch wheels, with no ROCm install
required. See [docs/running-on-amd-rocm.md](docs/running-on-amd-rocm.md).
- macOS support targets Apple Silicon only.
- macOS uses native window controls. Windows and Linux keep the existing custom controls.
- The top bar includes a live RAM indicator sourced from the main process.
Expand Down Expand Up @@ -106,11 +109,188 @@ Modly supports external model and process extensions. Each extension is a GitHub

![Install models](docs/install-models.png)

### Multiple Hugging Face repositories per model node

A model node whose weights are split across repositories can declare
`model_sources`. Modly validates every source, downloads them sequentially in
one Models-page action, and considers the node installed only when every
declared check exists.

```json
{
"id": "generate",
"model_sources": [
{
"id": "primary",
"provider": "huggingface",
"repo_id": "org/main-model",
"destination": ".",
"checks": ["model.safetensors"]
},
{
"id": "encoder",
"provider": "huggingface",
"repo_id": "org/encoder",
"revision": "v1.0",
"destination": "auxiliary/encoder",
"include_prefixes": ["config.json", "model.safetensors"],
"checks": ["config.json", "model.safetensors"]
}
]
}
```

`destination`, filters, and checks use safe POSIX paths relative to the node's
model directory. Every check must name a regular, non-empty file included by
that source's filters; invalid plans fail before any file is downloaded. Pin a
tag or commit in `revision` when reproducible weights are required. The only
supported provider is `huggingface`. Existing nodes that use `hf_repo`,
`download_check`, `hf_include_prefixes`, and `hf_skip_prefixes` keep their
original behavior.

### Shared weights inside one model extension

Multi-node model extensions can declare extension-scoped `weight_groups` and
reference them from any sibling node. Shared files are downloaded once under
`<models-dir>/<extension-id>/_shared/<group-id>`, while node-specific
`model_sources` stay under the node's existing model directory.

```json
{
"id": "pixal3d",
"type": "model",
"weight_groups": [
{
"id": "pixal3d-base",
"model_sources": [
{
"id": "base",
"provider": "huggingface",
"repo_id": "TencentARC/Pixal3D",
"revision": "<pinned-revision>",
"destination": ".",
"checks": ["pipeline.json"]
}
]
}
],
"nodes": [
{
"id": "generate",
"weight_groups": ["pixal3d-base"]
},
{
"id": "worldsculpt",
"weight_groups": ["pixal3d-base"],
"model_sources": [
{
"id": "adapter",
"provider": "huggingface",
"repo_id": "AlayaLab/WorldSculpt",
"revision": "<pinned-revision>",
"destination": ".",
"checks": ["model.safetensors"]
}
]
}
]
}
```

At runtime, `MODEL_DIR` remains the selected node's private directory.
Subprocess extensions also receive `MODEL_ID`, `MODEL_NODE_ID`, and a JSON
`SHARED_MODEL_DIRS` map in their environment. Both direct and subprocess generator
instances receive `MODEL_ID`, `MODEL_NODE_ID`, and the resolved mapping in
`shared_model_dirs` before `load()`. Direct generators use these instance attributes,
not process-global environment variables, to distinguish sibling nodes.
Shared groups are installed through their dependent nodes; the drawer exposes
shared-group status and explicit removal. Removing private node data never removes a shared group;
shared-group removal is a separate action that identifies every affected node.

### Separately installable weight variants

A model node that publishes the same weights in several variants (quantizations,
precisions…) can declare `weight_variants` next to `hf_repo`. The Extensions page
lists every variant under the node, and each one is downloaded or deleted on its own.

```json
{
"id": "generate",
"hf_repo": "org/model-gguf",
"download_check": "pipeline.json",
"hf_include_prefixes": ["pipeline.json", "encoder/", "dit/"],
"params_schema": [
{
"id": "quant",
"label": "Quantization",
"type": "select",
"default": "Q5_K_M",
"options": [
{ "value": "Q4_K_M", "label": "Q4_K_M" },
{ "value": "Q5_K_M", "label": "Q5_K_M" }
]
}
],
"weight_variants": {
"param": "quant",
"default": "Q5_K_M",
"options": [
{
"id": "Q4_K_M",
"label": "Q4_K_M",
"size_gb": 2.4,
"vram_gb": 6,
"include_prefixes": ["dit/model_Q4_K_M.gguf"],
"checks": ["dit/model_Q4_K_M.gguf"]
},
{
"id": "Q5_K_M",
"include_prefixes": ["dit/model_Q5_K_M.gguf"],
"checks": ["dit/model_Q5_K_M.gguf"]
}
]
}
}
```

- `param` names the `params_schema` select whose values are the variant ids. That
param must exist on the node (or on the extension, as its fallback), and when it
declares `options` they must cover every variant id.
- `size_gb` (download size) and `vram_gb` (approximate VRAM the variant needs) are
optional positive numbers, shown next to the variant when present.
- Every install downloads the shared files (`hf_include_prefixes`, with every
variant's files excluded automatically) plus one variant: the one asked for, or the
`default` one — the first option when `default` is omitted. Files already complete
on disk are skipped, so adding a second variant only fetches that variant.
Only declared variants are excluded from the shared pass: keep
`hf_include_prefixes` narrow enough that a variant the repository publishes but
the manifest does not declare (e.g. an extra `dit/model_Q8_0.gguf`) is not
downloaded with every install.
- A variant is installed when all of its `checks` exist; the node is installed once
its `download_check` and at least one variant are present.
- Generation fails with an explicit message when the selected variant is not
installed, and the node's selector labels those options `(not installed)`.
- `include_prefixes` and `checks` are safe POSIX paths relative to the node's model
directory. Prefixes of two variants cannot overlap, `download_check` stays outside
every variant, and `weight_variants` cannot be combined with `model_sources`.

---

## Workflows
Start with a basic workflow first. For example, on the "Workflows" tab, try: Image -> Generate Mesh -> Add to Scene. Make sure there is a connection between each of the steps. Go to the "Generate" tab, make sure the workflow is selected, then click on "Generate 3D Model". Click on "Settings/Logs/Errors" to see any issues.

Model extensions may also declare `scene` as a node input or output. A scene is
a workspace directory containing `scene-manifest.json` with schema
`modly.scene-manifest.v1`; it is not an arbitrary JSON file. Use the **Load
Scene** workflow node to select and validate an existing scene directory.
Scene-capable generators implement `generate_artifact(input_kind,
artifact_path, ...)`; legacy image generators and `POST /generate/from-image`
remain unchanged. The generic `POST /generate/from-artifact` boundary currently
accepts only `scene`, leaving future artifact kinds to separate reviewed changes.
For this first contract, `scene` is model-only and must be declared as the single
`input` value (not inside `inputs`); process and mixed-input scene nodes are rejected.
Model nodes may still accept multiple images and produce a scene.


## Modly CLI

Expand Down
1 change: 1 addition & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ uvicorn main:app --host 127.0.0.1 --port 8765 --reload
| GET | `/model/status` | Model download / load status |
| GET | `/model/download` | SSE stream of download progress |
| POST | `/generate/from-image` | Start image-to-3D job |
| POST | `/generate/from-artifact` | Start a typed-artifact model job (`scene` only) |
| GET | `/generate/status/{job_id}` | Poll job status |

## Model
Expand Down
9 changes: 6 additions & 3 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from services.stdio_utf8 import ensure_utf8_stdio
ensure_utf8_stdio() # must run before any print/logging hits the pipe

from routers import generation, model, optimize, status, settings, extensions, export, workflow_runs, agent
from routers import generation, model, optimize, status, settings, extensions, export, workflow_runs, agent, llm


@asynccontextmanager
Expand All @@ -21,8 +21,10 @@ async def lifespan(app: FastAPI):
from services.generator_registry import generator_registry
generator_registry.initialize()
yield
# Shutdown: unload all models
# Shutdown: unload all models and stop the local LLM server
generator_registry.unload_all()
from services.llm_server import llama_pool
llama_pool.unload_all(force=True)


class _StatusFilter(logging.Filter):
Expand All @@ -34,7 +36,7 @@ def filter(self, record):

app = FastAPI(
title="Modly API",
version="0.4.2",
version="0.4.3",
lifespan=lifespan,
)

Expand All @@ -57,6 +59,7 @@ def filter(self, record):
app.include_router(export.router, prefix="/export")
app.include_router(workflow_runs.router, prefix="/workflow-runs")
app.include_router(agent.router)
app.include_router(llm.router, prefix="/llm")

# Serve generated files from workspace — dynamic so path changes take effect immediately
@app.get("/workspace/{full_path:path}")
Expand Down
Loading
Loading