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 - - - Star History Chart + + + Star History Chart 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" /> +
+ ))} +
+