Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
+144
-13
@@ -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<FineTuning>) => 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<ModelStore, 'models' | 'activeModelId'>): { 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<ModelStore>((set) => ({
|
||||
export const useModelStore = create<ModelStore>((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<ModelStore>((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 = {
|
||||
|
||||
Reference in New Issue
Block a user