diff --git a/frontend/dcViewer.js b/frontend/dcViewer.js
index c417787..282eb22 100644
--- a/frontend/dcViewer.js
+++ b/frontend/dcViewer.js
@@ -319,9 +319,11 @@ let modelVersionHistoryLoading = false;
let modelVersionUploading = false;
let versionHistoryModal = null;
let loadModelAsyncRef = null;
+let versionModelCounter = 0;
let emptyWheelNavigationRestoreTimer = null;
let lastSurfaceDollyFactor = null;
let lastSurfaceDollyFactorAt = 0;
+const MODEL_LOAD_TIMEOUT_MS = 120000;
const GUEST_TOOLBAR_ACTIONS = new Set(["measure", "section", "projection"]);
const memoryStore = new Map(); // url -> ArrayBuffer
const blobUrls = [];
@@ -4700,6 +4702,13 @@ const uploadBuffer = async (bytes, filename, options = {}) => {
const getVersionKey = (version) => version?.versionId || `version-${version?.version || "unknown"}`;
+const makeVersionModelId = (version) => {
+ versionModelCounter += 1;
+ const rawKey = version?.versionId || version?.version || version?.originalFilename || versionModelCounter;
+ const safeKey = String(rawKey).replace(/[^a-z0-9_-]+/gi, "-").replace(/^-+|-+$/g, "") || "version";
+ return `model-version-${safeKey}-${versionModelCounter}`;
+};
+
const getModelVersionIdentity = (entry = getPrimaryModelEntry()) => {
const meta = entry?.meta || {};
const direct = {
@@ -4896,13 +4905,18 @@ const openModelVersion = async (version) => {
if (settings?.viewerSettings) {
meta.viewerSettings = settings.viewerSettings;
}
+ const previousModelId = previousEntry?.model?.id || null;
+ const nextModelId = makeVersionModelId(version);
+ if (previousModelId && transformStore[previousModelId]) {
+ transformStore[nextModelId] = safeClone(transformStore[previousModelId]);
+ }
await loadModelAsyncRef({
type: viewerType,
url: viewerSrc,
name: version.originalFilename || previousEntry?.name || "model",
replace: true,
preserveMeta: true,
- id: previousEntry?.model?.id || "model",
+ id: nextModelId,
meta,
});
modelVersionHistory = {
@@ -6093,6 +6107,7 @@ const initViewer = async () => {
isMemory,
name,
replace,
+ requestedId: forcedId || null,
url,
});
const xktDataSource = inferredType === "xkt"
@@ -6112,7 +6127,7 @@ const initViewer = async () => {
dataSource: xktDataSource || gltfDataSource || lasDataSource || stlDataSource || undefined,
});
- const id = forcedId || (replace ? "model" : `model-${++modelCounter}`);
+ const requestedId = forcedId || (replace ? "model" : `model-${++modelCounter}`);
let srcToLoad = url;
if (inferredType === "obj" && typeof url === "string" && url.startsWith("memory:") && memoryStore.has(url)) {
const blobUrl = URL.createObjectURL(new Blob([memoryStore.get(url)], { type: "text/plain" }));
@@ -6120,7 +6135,7 @@ const initViewer = async () => {
srcToLoad = blobUrl;
}
const loadOpts = {
- id,
+ id: requestedId,
src: srcToLoad,
edges: edges ?? (inferredType === "gltf" || inferredType === "las" ? false : toggleEdges.checked),
};
@@ -6129,12 +6144,17 @@ const initViewer = async () => {
if (inferredType === "stl") loadOpts.smoothNormals = true;
const currentModel = currentLoader.load(loadOpts);
+ const id = currentModel?.id || requestedId;
+ if (id !== requestedId && transformStore[requestedId] && !transformStore[id]) {
+ transformStore[id] = safeClone(transformStore[requestedId]);
+ }
sceneModels[id] = currentModel;
modelTypes[id] = inferredType;
currentModel.on("loaded", () => {
reportViewerEvent("model-loaded", {
id,
+ requestedId,
inferredType,
name,
objectCount: Object.keys(viewer.scene.objects || {}).length,
@@ -6180,6 +6200,7 @@ const initViewer = async () => {
currentModel.on("error", (err) => {
reportViewerEvent("model-error", {
id,
+ requestedId,
inferredType,
name,
error: err?.message || String(err),
@@ -6225,8 +6246,24 @@ const initViewer = async () => {
reject(new Error("Не удалось инициировать загрузку модели"));
return;
}
- model.on("loaded", () => resolve(model));
- model.on("error", (err) => reject(new Error(err?.message || err)));
+ let settled = false;
+ let timer = null;
+ const settle = (handler, value) => {
+ if (settled) return;
+ settled = true;
+ if (timer) window.clearTimeout(timer);
+ handler(value);
+ };
+ model.on("loaded", () => settle(resolve, model));
+ model.on("error", (err) => settle(reject, new Error(err?.message || err)));
+ if (!settled) {
+ timer = window.setTimeout(() => {
+ setLoading(false);
+ updateNavCubeVisibility();
+ updatePanelsVisibility();
+ settle(reject, new Error("Загрузка модели заняла слишком много времени"));
+ }, MODEL_LOAD_TIMEOUT_MS);
+ }
});
loadModelAsyncRef = loadModelAsync;
diff --git a/frontend/index.html b/frontend/index.html
index f802211..610794f 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -697,6 +697,6 @@
-
+