From e8433bab7a4e85cdab4dbaed7f107fbd6835cb5b Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 16:42:13 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/stores/useModelStore.ts | 157 +++++++++++++++++++++++++++++++++--- 1 file changed, 144 insertions(+), 13 deletions(-) diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts index 6ddfdf9..c4f771e 100644 --- a/src/stores/useModelStore.ts +++ b/src/stores/useModelStore.ts @@ -37,6 +37,20 @@ interface FineTuning { scale: number; } +export interface SceneModel { + id: string; + fileName: string; + fileSize: number; + url: string; + color: string; + visible: boolean; + locked: boolean; + fineTuning: FineTuning; +} + +const MAX_MODELS = 5; +const MODEL_COLORS = ['#3b82f6', '#22c55e', '#eab308', '#a855f7', '#ec4899']; + type AnchorMode = 'tracking' | 'manual' | 'fine-tuning'; type InspectionResult = 'pending' | 'approved' | 'rejected'; type RenderMode = 'solid' | 'wireframe' | 'edges'; @@ -75,17 +89,32 @@ const defaultChecklist: ChecklistItem[] = [ { id: 'weld', label: 'Especificação da Solda', status: 'pending', note: '', audioUrl: null }, ]; +const defaultFineTuning: FineTuning = { posX: 0, posY: 0, posZ: 0, rotX: 0, rotY: 0, rotZ: 0, scale: 1 }; + interface ModelStore { + // ── Multi-model scene ───────────── + models: SceneModel[]; + activeModelId: string | null; + addModel: (m: ModelInfo) => string; // returns id + removeModel: (id: string) => void; + setActiveModel: (id: string | null) => void; + toggleModelVisible: (id: string) => void; + toggleModelLocked: (id: string) => void; + duplicateModel: (id: string) => void; + clearAllModels: () => void; + maxModels: number; + + // ── Legacy single-model accessors (proxy to active) ───────────── model: ModelInfo | null; setModel: (model: ModelInfo | null) => void; - + fineTuning: FineTuning; setFineTuning: (ft: Partial) => void; resetFineTuning: () => void; - + anchorMode: AnchorMode; setAnchorMode: (mode: AnchorMode) => void; - + inspectionResult: InspectionResult; setInspectionResult: (result: InspectionResult) => void; @@ -144,19 +173,123 @@ interface ModelStore { clearScreenshots: () => void; } -const defaultFineTuning: FineTuning = { posX: 0, posY: 0, posZ: 0, rotX: 0, rotY: 0, rotZ: 0, scale: 1 }; +/** Sync top-level legacy `model` / `fineTuning` from active SceneModel. */ +function syncActive(state: Pick): { model: ModelInfo | null; fineTuning: FineTuning } { + const active = state.models.find(m => m.id === state.activeModelId) ?? null; + return { + model: active ? { fileName: active.fileName, fileSize: active.fileSize, url: active.url } : null, + fineTuning: active ? { ...active.fineTuning } : { ...defaultFineTuning }, + }; +} -export const useModelStore = create((set) => ({ +export const useModelStore = create((set, get) => ({ + // ── Multi-model ───────────── + models: [], + activeModelId: null, + maxModels: MAX_MODELS, + + addModel: (m) => { + const state = get(); + if (state.models.length >= MAX_MODELS) { + return ''; + } + const id = `mdl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; + const color = MODEL_COLORS[state.models.length % MODEL_COLORS.length]; + // Stagger position so multiple models don't perfectly overlap + const offsetIdx = state.models.length; + const newModel: SceneModel = { + id, + fileName: m.fileName, + fileSize: m.fileSize, + url: m.url, + color, + visible: true, + locked: false, + fineTuning: { ...defaultFineTuning, posX: offsetIdx * 0.15 }, + }; + const models = [...state.models, newModel]; + set({ models, activeModelId: id, ...syncActive({ models, activeModelId: id }) }); + return id; + }, + + removeModel: (id) => { + const state = get(); + const models = state.models.filter(m => m.id !== id); + const activeModelId = state.activeModelId === id + ? (models[0]?.id ?? null) + : state.activeModelId; + set({ models, activeModelId, ...syncActive({ models, activeModelId }) }); + }, + + setActiveModel: (id) => { + const state = get(); + set({ activeModelId: id, ...syncActive({ models: state.models, activeModelId: id }) }); + }, + + toggleModelVisible: (id) => set((state) => ({ + models: state.models.map(m => m.id === id ? { ...m, visible: !m.visible } : m), + })), + + toggleModelLocked: (id) => set((state) => ({ + models: state.models.map(m => m.id === id ? { ...m, locked: !m.locked } : m), + })), + + duplicateModel: (id) => { + const state = get(); + if (state.models.length >= MAX_MODELS) return; + const src = state.models.find(m => m.id === id); + if (!src) return; + const newId = `mdl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; + const color = MODEL_COLORS[state.models.length % MODEL_COLORS.length]; + const copy: SceneModel = { + ...src, + id: newId, + color, + fineTuning: { ...src.fineTuning, posX: src.fineTuning.posX + 0.15 }, + }; + const models = [...state.models, copy]; + set({ models, activeModelId: newId, ...syncActive({ models, activeModelId: newId }) }); + }, + + clearAllModels: () => set({ models: [], activeModelId: null, model: null, fineTuning: { ...defaultFineTuning } }), + + // ── Legacy single-model proxies ───────────── model: null, - setModel: (model) => set({ model }), - + setModel: (m) => { + if (!m) { + set({ models: [], activeModelId: null, model: null, fineTuning: { ...defaultFineTuning } }); + return; + } + // Legacy: replace all models with this single one + const id = `mdl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; + const newModel: SceneModel = { + id, fileName: m.fileName, fileSize: m.fileSize, url: m.url, + color: MODEL_COLORS[0], visible: true, locked: false, + fineTuning: { ...defaultFineTuning }, + }; + set({ models: [newModel], activeModelId: id, ...syncActive({ models: [newModel], activeModelId: id }) }); + }, + fineTuning: { ...defaultFineTuning }, - setFineTuning: (ft) => set((state) => ({ fineTuning: { ...state.fineTuning, ...ft } })), - resetFineTuning: () => set({ fineTuning: { ...defaultFineTuning } }), - + setFineTuning: (ft) => set((state) => { + const newFT = { ...state.fineTuning, ...ft }; + // Also write to the active model's fineTuning + const models = state.activeModelId + ? state.models.map(m => m.id === state.activeModelId ? { ...m, fineTuning: newFT } : m) + : state.models; + return { fineTuning: newFT, models }; + }), + resetFineTuning: () => set((state) => { + const reset = { ...defaultFineTuning }; + const models = state.activeModelId + ? state.models.map(m => m.id === state.activeModelId ? { ...m, fineTuning: reset } : m) + : state.models; + return { fineTuning: reset, models }; + }), + anchorMode: 'manual', setAnchorMode: (anchorMode) => set({ anchorMode }), - + inspectionResult: 'pending', setInspectionResult: (inspectionResult) => set({ inspectionResult }), @@ -213,8 +346,6 @@ export const useModelStore = create((set) => ({ const dy = (a.y - b.y) * 1000; const dz = (a.z - b.z) * 1000; const distanceWorldMM = Math.sqrt(dx * dx + dy * dy + dz * dz); - // Compensate for current render scale: world ≠ real when scaled. - // realMM = worldMM / factor (factor 1/50 → multiply by 50). const factor = state.scaleRatio?.factor ?? 1; const distanceMM = distanceWorldMM / factor; const measurement: Measurement = {