Files
DotsToSirface/frontend/web/src/stores/dataset.js
T
gitrusprusandCursor 4f253b860f Реструктуризация проекта и генератор синтетических датасетов эхолота.
Перенесены backend/frontend/desktop/engine, добавлены вкладки конструктора сцен и генератора датасета с параметрами лучей и длины сетки рельефа, обновлены API и Docker-сборка.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:25:00 +03:00

216 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
},
},
});