import { defineStore } from "pinia"; import { api } from "@/api/client"; export const useDatasetStore = defineStore("dataset", { state: () => ({ busy: false, previewBusy: false, statusText: "Выберите .obj модель и нажмите «Сгенерировать».", count: 5, seed: 42, outputDir: "sonar_dataset", resolvedOutputDir: null, modelFile: null, modelFileName: "", objectScale: 1, beamCount: 45, lengthCount: 45, lastResult: null, selectedStem: null, previewPoints: [], previewStem: null, previewMeta: null, highlightClass: null, classLabels: { 0: "background", 1: "object" }, classCounts: {}, logLines: [], }), getters: { stats(state) { return state.lastResult?.stats || null; }, written(state) { return state.lastResult?.written || []; }, previewDir(state) { return state.resolvedOutputDir || state.outputDir || "sonar_dataset"; }, selectedScene(state) { const stem = state.selectedStem; if (!stem) return null; return (state.lastResult?.written || []).find((item) => item.stem === stem) || null; }, canGenerate(state) { return !!state.modelFile && !state.busy; }, classOptions(state) { const labels = state.classLabels || { 0: "background", 1: "object" }; const counts = state.classCounts || {}; const keys = Object.keys(labels).length ? Object.keys(labels) : Object.keys(counts); return keys .map((k) => Number(k)) .sort((a, b) => a - b) .map((id) => ({ id, label: labels[String(id)] || labels[id] || `class ${id}`, count: counts[String(id)] ?? counts[id] ?? 0, })); }, }, actions: { pushLog(line) { this.logLines.push(String(line)); if (this.logLines.length > 200) { this.logLines = this.logLines.slice(-200); } }, setHighlightClass(value) { if (value === null || value === undefined || value === "" || value === "all") { this.highlightClass = null; return; } this.highlightClass = Number(value); }, applyPreviewPayload(result, writtenMeta = null) { this.previewStem = result.stem; this.previewPoints = result.points || []; this.classCounts = result.classCounts || {}; if (result.classLabels) { this.classLabels = result.classLabels; } this.previewMeta = { pointCount: result.pointCount, previewCount: result.previewCount, visibility: writtenMeta?.visibility, hasObject: writtenMeta?.hasObject, objectPointCount: writtenMeta?.objectPointCount, classCounts: result.classCounts || {}, }; }, setModelFile(file) { if (!file) { this.modelFile = null; this.modelFileName = ""; this.statusText = "Выберите .obj модель и нажмите «Сгенерировать»."; return; } const name = String(file.name || ""); if (!name.toLowerCase().endsWith(".obj")) { this.modelFile = null; this.modelFileName = ""; this.statusText = "Нужен файл формата .obj"; return; } this.modelFile = file; this.modelFileName = name; this.statusText = `Модель: ${name}`; this.pushLog(`Выбрана модель: ${name}`); }, async generate() { if (this.busy) return; if (!this.modelFile) { this.statusText = "Сначала выберите файл модели .obj"; return; } this.busy = true; this.statusText = "Генерация датасета…"; this.pushLog( `Старт: count=${this.count}, seed=${this.seed}, beams=${this.beamCount}, length=${this.lengthCount}, scale=${this.objectScale}, dir=${this.outputDir}, model=${this.modelFileName}`, ); try { const result = await api.datasetGenerate({ count: Number(this.count) || 5, seed: Number(this.seed) || 0, outputDir: String(this.outputDir || "sonar_dataset"), objectScale: Number(this.objectScale) || 1, beamCount: Number(this.beamCount) || 45, lengthCount: Number(this.lengthCount) || 45, modelFile: this.modelFile, }); this.lastResult = result; this.resolvedOutputDir = result?.outputDir || null; const s = result?.stats || {}; this.statusText = `Готово: ${result.count} сцен → ${result.outputDir}`; this.pushLog( `Модель: ${result.objectName || this.modelFileName} (${result.objectVertexCount || "?"} вершин), scale=${result.objectScale ?? this.objectScale}, ширина=${result.beamCount ?? this.beamCount}, длина=${result.lengthCount ?? this.lengthCount}. class 1 = object.`, ); this.pushLog( `Записано ${result.count} сцен. С объектом: ${s.withObject}, без: ${s.withoutObject}.`, ); this.pushLog( `Видимость: nearly_hidden=${s.nearly_hidden || 0}, partial=${s.partial || 0}, visible=${s.visible || 0}, absent=${s.absent || 0}.`, ); for (const item of result.written || []) { this.pushLog( `${item.stem}: pts=${item.pointCount}, object=${item.objectPointCount}, ${item.visibility}`, ); } const initialStem = result?.preview?.stem || result?.written?.find((item) => item.hasObject)?.stem || result?.written?.[0]?.stem || null; if (result?.preview?.points?.length && result.preview.stem === initialStem) { this.selectedStem = initialStem; const meta = result.written?.find((w) => w.stem === initialStem); this.applyPreviewPayload( { stem: initialStem, points: result.preview.points, pointCount: result.preview.pointCount, previewCount: result.preview.points.length, classCounts: result.preview.classCounts || {}, classLabels: result.preview.classLabels || result.classLabels, }, meta, ); } else if (initialStem) { await this.selectScene(initialStem); } else { this.selectedStem = null; this.previewStem = null; this.previewPoints = []; this.previewMeta = null; this.classCounts = {}; } } catch (error) { this.statusText = `Ошибка: ${error.message}`; this.pushLog(`Ошибка: ${error.message}`); throw error; } finally { this.busy = false; } }, async selectScene(stem) { if (!stem || this.previewBusy) return; if (stem === this.previewStem && this.previewPoints?.length) { this.selectedStem = stem; return; } this.previewBusy = true; this.selectedStem = stem; this.statusText = `Загрузка превью: ${stem}…`; try { const result = await api.datasetPreview({ stem, outputDir: String(this.previewDir), }); const writtenMeta = this.written.find((item) => item.stem === result.stem); this.applyPreviewPayload(result, writtenMeta); this.statusText = `Превью: ${result.stem} (${result.pointCount} точек)`; this.pushLog(`Превью загружено: ${result.stem} (${result.pointCount} pts)`); } catch (error) { this.statusText = `Ошибка превью: ${error.message}`; this.pushLog(`Ошибка превью: ${error.message}`); throw error; } finally { this.previewBusy = false; } }, }, });