diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d845e551..80721d25 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,3 +37,22 @@ jobs:
run: sudo apt-get install -y rpm
- name: Build & Package (Linux)
run: npx electron-vite build && npx electron-builder --linux --publish never
+
+ # Même build que release.yml (arm64, signature ad-hoc via after-pack.js),
+ # sans publication : vérifie juste que le packaging macOS ne casse pas.
+ build-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ - name: Install dependencies
+ run: npm install
+ - name: Download Python embed
+ run: node scripts/download-python-embed.js
+ - name: Build & Package (macOS arm64)
+ run: npx electron-vite build && npx electron-builder --mac --arm64 --publish never
+ env:
+ CSC_IDENTITY_AUTO_DISCOVERY: false
diff --git a/README.md b/README.md
index 0e0ef792..455cbe90 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Alternatively, you can clone the repository and run the app directly without ins
launch.bat
# Linux / macOS
-./launcher.sh
+./launch.sh
```
---
@@ -108,6 +108,10 @@ Modly supports external model and process extensions. Each extension is a GitHub
---
+## 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.
+
+
## Modly CLI
Agents and scripts can call a running Modly desktop app without using the UI via the stdlib-only CLI. The CLI is a thin helper over Modly's canonical automation concepts and keeps final machine-readable JSON on stdout:
@@ -177,8 +181,8 @@ This is a requirement of the MIT license attribution clause. Please keep this cr
-
-
-
+
+
+
diff --git a/api/main.py b/api/main.py
index e73b3f2a..84681b0e 100644
--- a/api/main.py
+++ b/api/main.py
@@ -31,7 +31,7 @@ def filter(self, record):
app = FastAPI(
title="Modly API",
- version="0.4.0",
+ version="0.4.1",
lifespan=lifespan,
)
diff --git a/package.json b/package.json
index e26d7dcf..64ef178f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "modly",
- "version": "0.4.0",
+ "version": "0.4.1",
"description": "Local AI-powered 3D mesh generation from images",
"main": "./out/main/index.js",
"author": "Modly",
diff --git a/src/areas/generate/GeneratePage.tsx b/src/areas/generate/GeneratePage.tsx
index a6dbe4f3..6ce5e894 100644
--- a/src/areas/generate/GeneratePage.tsx
+++ b/src/areas/generate/GeneratePage.tsx
@@ -1,7 +1,7 @@
import { useState, useRef, useCallback, useEffect, useMemo } from 'react'
import type { ReactNode } from 'react'
import { useAppStore, DEFAULT_LIGHT_SETTINGS } from '@shared/stores/appStore'
-import type { GenerationJob, LightSettings } from '@shared/stores/appStore'
+import type { GenerationJob, LightSettings, PointLight } from '@shared/stores/appStore'
import { useApi } from '@shared/hooks/useApi'
import { ColorPicker } from '@shared/components/ui'
import GenerationHUD from './components/GenerationHUD'
@@ -27,6 +27,18 @@ const MIN_WIDTH = 220
const MAX_WIDTH = 520
const DEFAULT_WIDTH = 320
+const MAX_POINT_LIGHTS = 6
+
+function createPointLight(): PointLight {
+ const angle = Math.random() * Math.PI * 2
+ return {
+ id: crypto.randomUUID(),
+ position: [Math.cos(angle) * 1.5, 0.5, Math.sin(angle) * 1.5],
+ color: '#ffffff',
+ intensity: 1,
+ }
+}
+
// ---------------------------------------------------------------------------
// Export dropdown
// ---------------------------------------------------------------------------
@@ -179,10 +191,16 @@ function LightPopover({
settings,
onChange,
onClose,
+ pointLights,
+ onPointLightsChange,
+ onSelectPointLight,
}: {
settings: LightSettings
onChange: (s: LightSettings) => void
onClose: () => void
+ pointLights: PointLight[]
+ onPointLightsChange: (lights: PointLight[]) => void
+ onSelectPointLight: (id: string | null) => void
}) {
function lightRow(
label: string,
@@ -251,6 +269,53 @@ function LightPopover({
{lightRow('Fill', 'fillColor', 'fillIntensity', 2)}
{plainRow('Ambient', 'ambientIntensity', 1.5)}
{plainRow('Environment', 'envIntensity', 2)}
+
+ {/* ── Point lights ── */}
+
+
+
Point lights
+ {pointLights.length < MAX_POINT_LIGHTS && (
+
+ )}
+
+
+ {pointLights.length === 0 && (
+
No point lights yet.
+ )}
+
+ {pointLights.map((pl) => (
+
+
+ onPointLightsChange(pointLights.map((p) => p.id === pl.id ? { ...p, color: c } : p))}
+ />
+ Point
+ {pl.intensity.toFixed(1)}
+
+
+
onPointLightsChange(pointLights.map((p) => p.id === pl.id ? { ...p, intensity: parseFloat(e.target.value) } : p))}
+ className="w-full h-1.5 accent-violet-500 cursor-pointer" />
+
+ ))}
+
+