3dviewer-sdk 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -36,6 +36,8 @@ declare enum ViewerMessageType {
36
36
  TREE_SELECT_NODE = "viewer-tree-select-node",
37
37
  TREE_GET_NODE_IDS = "viewer-tree-get-node-ids",
38
38
  TREE_NODE_IDS = "viewer-tree-node-ids",
39
+ TREE_GET_NODES = "viewer-tree-get-nodes",
40
+ TREE_NODES = "viewer-tree-nodes",
39
41
  PDF_PLAN_MODE = "viewer-pdf-plan-mode",
40
42
  PDF_DOCUMENT_MODE = "viewer-pdf-document-mode",
41
43
  PDF_FIRST_PAGE = "viewer-pdf-first-page",
@@ -91,6 +93,19 @@ type StateObjectItem = {
91
93
  type: string;
92
94
  };
93
95
  };
96
+ type TreeNodeItem = {
97
+ id: string;
98
+ name: string;
99
+ type: number;
100
+ parent?: string;
101
+ childs?: string[];
102
+ modelFileId?: string;
103
+ persistentId?: string;
104
+ categoryName?: string;
105
+ familyName?: string;
106
+ typeName?: string;
107
+ isRealNode: boolean;
108
+ };
94
109
  type PdfModeEventPayload = {
95
110
  mode: "plan" | "document";
96
111
  timestamp: number;
@@ -127,6 +142,12 @@ type ViewerEventMap = {
127
142
  nodeIds: string[];
128
143
  timestamp: number;
129
144
  };
145
+ "modelTree:nodes": {
146
+ requestId: string;
147
+ nodes: TreeNodeItem[];
148
+ rootNodeIds?: string[];
149
+ timestamp: number;
150
+ };
130
151
  "sheets:list": {
131
152
  requestId: string;
132
153
  sheets: {
@@ -237,8 +258,7 @@ declare class InteractionModule {
237
258
  }) => void) => () => void;
238
259
  };
239
260
  constructor(viewer: Viewer3D);
240
- enablePan(): void;
241
- disablePan(): void;
261
+ pan(): void;
242
262
  select(): void;
243
263
  areaSelect(): void;
244
264
  orbit(): void;
@@ -270,6 +290,7 @@ declare class NodeModule {
270
290
 
271
291
  type FilesConfig = {
272
292
  baseUrl?: string;
293
+ conversionUrl?: string;
273
294
  viewerPath?: string;
274
295
  uploadPath?: string;
275
296
  };
@@ -358,7 +379,7 @@ declare class FilesModule {
358
379
  render(file?: File, options?: ConvertOptions): Promise<PreparedViewerData>;
359
380
  private resolveFile;
360
381
  private normalizeBaseUrl;
361
- private resolveBaseUrl;
382
+ private resolveConversionUrl;
362
383
  private resolveViewerPath;
363
384
  private normalizeSdkViewerPath;
364
385
  private resolveViewerOrigin;
@@ -475,16 +496,30 @@ declare class ToolbarModule {
475
496
  private postStatesObjectsGetList;
476
497
  }
477
498
 
478
- type GetNodeIdsOptions = {
499
+ type ModelTreeRequestOptions = {
479
500
  onlyRealNodes?: boolean;
480
501
  timeoutMs?: number;
481
502
  };
503
+ type GetNodeIdsOptions = ModelTreeRequestOptions;
504
+ type GetNodesOptions = ModelTreeRequestOptions;
505
+ type ModelTreeNode = TreeNodeItem;
506
+ type ModelTreeHierarchyNode = ModelTreeNode & {
507
+ children: ModelTreeHierarchyNode[];
508
+ };
482
509
  declare class ModelTreeModule {
483
510
  private viewer;
484
511
  constructor(viewer: Viewer3D);
485
512
  open(): void;
486
513
  selectNode(nodeId: string | number): void;
487
514
  getNodeIds(options?: GetNodeIdsOptions): Promise<string[]>;
515
+ getNodes(options?: GetNodesOptions): Promise<ModelTreeNode[]>;
516
+ getTree(options?: GetNodesOptions): Promise<ModelTreeHierarchyNode[]>;
517
+ private requestNodes;
518
+ private resolveTimeoutMs;
519
+ private postPanelOpen;
520
+ private postTreeSelectNode;
521
+ private postTreeGetNodeIds;
522
+ private postTreeGetNodes;
488
523
  }
489
524
 
490
525
  type MarkupRequestOptions = {
@@ -524,6 +559,7 @@ type Viewer3DOptions = {
524
559
  container: HTMLElement | string;
525
560
  url?: string;
526
561
  baseUrl?: string;
562
+ conversionUrl?: string;
527
563
  viewerPath?: string;
528
564
  uploadPath?: string;
529
565
  file?: File;
package/dist/index.d.ts CHANGED
@@ -36,6 +36,8 @@ declare enum ViewerMessageType {
36
36
  TREE_SELECT_NODE = "viewer-tree-select-node",
37
37
  TREE_GET_NODE_IDS = "viewer-tree-get-node-ids",
38
38
  TREE_NODE_IDS = "viewer-tree-node-ids",
39
+ TREE_GET_NODES = "viewer-tree-get-nodes",
40
+ TREE_NODES = "viewer-tree-nodes",
39
41
  PDF_PLAN_MODE = "viewer-pdf-plan-mode",
40
42
  PDF_DOCUMENT_MODE = "viewer-pdf-document-mode",
41
43
  PDF_FIRST_PAGE = "viewer-pdf-first-page",
@@ -91,6 +93,19 @@ type StateObjectItem = {
91
93
  type: string;
92
94
  };
93
95
  };
96
+ type TreeNodeItem = {
97
+ id: string;
98
+ name: string;
99
+ type: number;
100
+ parent?: string;
101
+ childs?: string[];
102
+ modelFileId?: string;
103
+ persistentId?: string;
104
+ categoryName?: string;
105
+ familyName?: string;
106
+ typeName?: string;
107
+ isRealNode: boolean;
108
+ };
94
109
  type PdfModeEventPayload = {
95
110
  mode: "plan" | "document";
96
111
  timestamp: number;
@@ -127,6 +142,12 @@ type ViewerEventMap = {
127
142
  nodeIds: string[];
128
143
  timestamp: number;
129
144
  };
145
+ "modelTree:nodes": {
146
+ requestId: string;
147
+ nodes: TreeNodeItem[];
148
+ rootNodeIds?: string[];
149
+ timestamp: number;
150
+ };
130
151
  "sheets:list": {
131
152
  requestId: string;
132
153
  sheets: {
@@ -237,8 +258,7 @@ declare class InteractionModule {
237
258
  }) => void) => () => void;
238
259
  };
239
260
  constructor(viewer: Viewer3D);
240
- enablePan(): void;
241
- disablePan(): void;
261
+ pan(): void;
242
262
  select(): void;
243
263
  areaSelect(): void;
244
264
  orbit(): void;
@@ -270,6 +290,7 @@ declare class NodeModule {
270
290
 
271
291
  type FilesConfig = {
272
292
  baseUrl?: string;
293
+ conversionUrl?: string;
273
294
  viewerPath?: string;
274
295
  uploadPath?: string;
275
296
  };
@@ -358,7 +379,7 @@ declare class FilesModule {
358
379
  render(file?: File, options?: ConvertOptions): Promise<PreparedViewerData>;
359
380
  private resolveFile;
360
381
  private normalizeBaseUrl;
361
- private resolveBaseUrl;
382
+ private resolveConversionUrl;
362
383
  private resolveViewerPath;
363
384
  private normalizeSdkViewerPath;
364
385
  private resolveViewerOrigin;
@@ -475,16 +496,30 @@ declare class ToolbarModule {
475
496
  private postStatesObjectsGetList;
476
497
  }
477
498
 
478
- type GetNodeIdsOptions = {
499
+ type ModelTreeRequestOptions = {
479
500
  onlyRealNodes?: boolean;
480
501
  timeoutMs?: number;
481
502
  };
503
+ type GetNodeIdsOptions = ModelTreeRequestOptions;
504
+ type GetNodesOptions = ModelTreeRequestOptions;
505
+ type ModelTreeNode = TreeNodeItem;
506
+ type ModelTreeHierarchyNode = ModelTreeNode & {
507
+ children: ModelTreeHierarchyNode[];
508
+ };
482
509
  declare class ModelTreeModule {
483
510
  private viewer;
484
511
  constructor(viewer: Viewer3D);
485
512
  open(): void;
486
513
  selectNode(nodeId: string | number): void;
487
514
  getNodeIds(options?: GetNodeIdsOptions): Promise<string[]>;
515
+ getNodes(options?: GetNodesOptions): Promise<ModelTreeNode[]>;
516
+ getTree(options?: GetNodesOptions): Promise<ModelTreeHierarchyNode[]>;
517
+ private requestNodes;
518
+ private resolveTimeoutMs;
519
+ private postPanelOpen;
520
+ private postTreeSelectNode;
521
+ private postTreeGetNodeIds;
522
+ private postTreeGetNodes;
488
523
  }
489
524
 
490
525
  type MarkupRequestOptions = {
@@ -524,6 +559,7 @@ type Viewer3DOptions = {
524
559
  container: HTMLElement | string;
525
560
  url?: string;
526
561
  baseUrl?: string;
562
+ conversionUrl?: string;
527
563
  viewerPath?: string;
528
564
  uploadPath?: string;
529
565
  file?: File;
package/dist/index.js CHANGED
@@ -80,11 +80,8 @@ var InteractionModule = class {
80
80
  panChange: (cb) => this.viewer._on("interaction:pan-change", cb)
81
81
  };
82
82
  }
83
- enablePan() {
84
- this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */, { enabled: true });
85
- }
86
- disablePan() {
87
- this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */, { enabled: false });
83
+ pan() {
84
+ this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */);
88
85
  }
89
86
  select() {
90
87
  this.viewer.postToViewer("viewer-select" /* SELECT */);
@@ -145,8 +142,8 @@ var NodeModule = class {
145
142
  };
146
143
 
147
144
  // src/modules/files.module.ts
148
- var DEFAULT_API_BASE_URL = "https://dev.3dviewer.anybim.vn/service/conversion";
149
- var DEFAULT_VIEWER_ORIGIN = "http://localhost:3000";
145
+ var DEFAULT_CONVERSION_URL = "https://dev.3dviewer.anybim.vn/service/conversion";
146
+ var DEFAULT_VIEWER_BASE_URL = "https://dev.3dviewer.anybim.vn";
150
147
  var SDK_VIEWER_PATH = "/mainviewer-sdk";
151
148
  var LEGACY_VIEWER_PATH = "/mainviewer";
152
149
  var FilesModule = class {
@@ -293,9 +290,9 @@ var FilesModule = class {
293
290
  normalizeBaseUrl(input) {
294
291
  return input.trim().replace(/\/+$/, "");
295
292
  }
296
- // Resolve API base URL with default fallback.
297
- resolveBaseUrl() {
298
- const raw = this.config.baseUrl || this.viewer.getOptions().baseUrl || DEFAULT_API_BASE_URL;
293
+ // Resolve conversion API URL with default fallback.
294
+ resolveConversionUrl() {
295
+ const raw = this.config.conversionUrl || this.viewer.getOptions().conversionUrl || DEFAULT_CONVERSION_URL;
299
296
  return this.normalizeBaseUrl(raw);
300
297
  }
301
298
  // Resolve viewer route path for all SDK flows.
@@ -321,26 +318,26 @@ var FilesModule = class {
321
318
  }
322
319
  // Viewer host used to open iframe after conversion completes.
323
320
  resolveViewerOrigin() {
324
- const viewerUrl = this.viewer.getOptions().url;
325
- if (viewerUrl) {
321
+ const configuredViewerBaseUrl = this.config.baseUrl || this.viewer.getOptions().baseUrl;
322
+ if (configuredViewerBaseUrl) {
326
323
  try {
327
- return this.normalizeBaseUrl(new URL(viewerUrl, window.location.href).origin);
324
+ return this.normalizeBaseUrl(new URL(configuredViewerBaseUrl, window.location.href).origin);
328
325
  } catch {
329
326
  }
330
327
  }
331
- const configuredBaseUrl = this.config.baseUrl || this.viewer.getOptions().baseUrl;
332
- if (configuredBaseUrl) {
328
+ const viewerUrl = this.viewer.getOptions().url;
329
+ if (viewerUrl) {
333
330
  try {
334
- return this.normalizeBaseUrl(new URL(configuredBaseUrl, window.location.href).origin);
331
+ return this.normalizeBaseUrl(new URL(viewerUrl, window.location.href).origin);
335
332
  } catch {
336
333
  }
337
334
  }
338
- return this.normalizeBaseUrl(DEFAULT_VIEWER_ORIGIN);
335
+ return this.normalizeBaseUrl(DEFAULT_VIEWER_BASE_URL);
339
336
  }
340
- // Resolve conversion service root from configured API base URL.
337
+ // Resolve conversion service root from configured conversion URL.
341
338
  // Do not auto-append path segments (e.g. "/service/conversion").
342
339
  resolveHostConversion() {
343
- return this.resolveBaseUrl();
340
+ return this.resolveConversionUrl();
344
341
  }
345
342
  // Resolve upload path sent to conversion APIs.
346
343
  getUploadPath() {
@@ -891,28 +888,56 @@ var ToolbarModule = class {
891
888
  };
892
889
 
893
890
  // src/modules/model-tree.module.ts
894
- function createRequestId2() {
895
- return `tree_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
891
+ function createRequestId2(prefix) {
892
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
893
+ }
894
+ function buildTree(nodes, rootNodeIds) {
895
+ const uniqueNodes = Array.from(
896
+ nodes.reduce((map, node) => {
897
+ map.set(node.id, node);
898
+ return map;
899
+ }, /* @__PURE__ */ new Map()),
900
+ ([, node]) => node
901
+ );
902
+ const hierarchyMap = new Map(
903
+ uniqueNodes.map((node) => [node.id, { ...node, children: [] }])
904
+ );
905
+ const childrenByParent = /* @__PURE__ */ new Map();
906
+ uniqueNodes.forEach((node) => {
907
+ var _a;
908
+ if (!node.parent) return;
909
+ const current = (_a = childrenByParent.get(node.parent)) != null ? _a : [];
910
+ current.push(node.id);
911
+ childrenByParent.set(node.parent, current);
912
+ });
913
+ hierarchyMap.forEach((node) => {
914
+ var _a, _b;
915
+ const childIdsFromNode = ((_a = node.childs) != null ? _a : []).filter((childId) => hierarchyMap.has(childId));
916
+ const fallbackChildIds = (_b = childrenByParent.get(node.id)) != null ? _b : [];
917
+ const childIds = childIdsFromNode.length > 0 ? childIdsFromNode : fallbackChildIds;
918
+ node.children = Array.from(new Set(childIds)).map((childId) => hierarchyMap.get(childId)).filter((child) => Boolean(child));
919
+ });
920
+ const rootIds = Array.isArray(rootNodeIds) && rootNodeIds.length > 0 ? rootNodeIds.filter((id) => hierarchyMap.has(id)) : uniqueNodes.filter((node) => !node.parent || !hierarchyMap.has(node.parent)).map((node) => node.id);
921
+ return Array.from(new Set(rootIds)).map((id) => hierarchyMap.get(id)).filter((node) => Boolean(node));
896
922
  }
897
923
  var ModelTreeModule = class {
898
924
  constructor(viewer) {
899
925
  this.viewer = viewer;
900
926
  }
901
927
  open() {
902
- this.viewer.postToViewer("viewer-panel-open" /* PANEL_OPEN */, {
928
+ this.postPanelOpen({
903
929
  panel: "model-tree",
904
930
  format: "3d"
905
931
  });
906
932
  }
907
933
  selectNode(nodeId) {
908
- this.viewer.postToViewer("viewer-tree-select-node" /* TREE_SELECT_NODE */, {
934
+ this.postTreeSelectNode({
909
935
  nodeId: String(nodeId)
910
936
  });
911
937
  }
912
938
  getNodeIds(options) {
913
- var _a;
914
- const requestId = createRequestId2();
915
- const timeoutMs = Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
939
+ const requestId = createRequestId2("tree");
940
+ const timeoutMs = this.resolveTimeoutMs(options);
916
941
  return new Promise((resolve, reject) => {
917
942
  const timer = setTimeout(() => {
918
943
  off();
@@ -924,12 +949,58 @@ var ModelTreeModule = class {
924
949
  off();
925
950
  resolve(payload.nodeIds);
926
951
  });
927
- this.viewer.postToViewer("viewer-tree-get-node-ids" /* TREE_GET_NODE_IDS */, {
952
+ this.postTreeGetNodeIds({
928
953
  requestId,
929
954
  onlyRealNodes: (options == null ? void 0 : options.onlyRealNodes) !== false
930
955
  });
931
956
  });
932
957
  }
958
+ getNodes(options) {
959
+ return this.requestNodes(options).then((response) => response.nodes);
960
+ }
961
+ getTree(options) {
962
+ return this.requestNodes(options).then((response) => buildTree(response.nodes, response.rootNodeIds));
963
+ }
964
+ requestNodes(options) {
965
+ const requestId = createRequestId2("tree_nodes");
966
+ const timeoutMs = this.resolveTimeoutMs(options);
967
+ return new Promise((resolve, reject) => {
968
+ const timer = setTimeout(() => {
969
+ off();
970
+ reject(new Error("Timeout while getting model tree nodes from viewer"));
971
+ }, timeoutMs);
972
+ const off = this.viewer._on("modelTree:nodes", (payload) => {
973
+ if (payload.requestId !== requestId) return;
974
+ clearTimeout(timer);
975
+ off();
976
+ resolve({
977
+ nodes: payload.nodes,
978
+ rootNodeIds: payload.rootNodeIds
979
+ });
980
+ });
981
+ this.postTreeGetNodes({
982
+ requestId,
983
+ // For backward compatibility, default includes helper/system nodes unless caller opts in.
984
+ onlyRealNodes: (options == null ? void 0 : options.onlyRealNodes) === true
985
+ });
986
+ });
987
+ }
988
+ resolveTimeoutMs(options) {
989
+ var _a;
990
+ return Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
991
+ }
992
+ postPanelOpen(payload) {
993
+ this.viewer.postToViewer("viewer-panel-open" /* PANEL_OPEN */, payload);
994
+ }
995
+ postTreeSelectNode(payload) {
996
+ this.viewer.postToViewer("viewer-tree-select-node" /* TREE_SELECT_NODE */, payload);
997
+ }
998
+ postTreeGetNodeIds(payload) {
999
+ this.viewer.postToViewer("viewer-tree-get-node-ids" /* TREE_GET_NODE_IDS */, payload);
1000
+ }
1001
+ postTreeGetNodes(payload) {
1002
+ this.viewer.postToViewer("viewer-tree-get-nodes" /* TREE_GET_NODES */, payload);
1003
+ }
933
1004
  };
934
1005
 
935
1006
  // src/modules/markup.module.ts
@@ -1131,6 +1202,32 @@ var Viewer3D = class {
1131
1202
  });
1132
1203
  break;
1133
1204
  }
1205
+ case "viewer-tree-nodes" /* TREE_NODES */: {
1206
+ const payload = data.payload;
1207
+ if (!payload || !payload.requestId || !Array.isArray(payload.nodes)) break;
1208
+ this._emit("modelTree:nodes", {
1209
+ requestId: String(payload.requestId),
1210
+ nodes: payload.nodes.filter((node) => node && typeof node === "object").map((node) => {
1211
+ var _a2, _b2;
1212
+ return {
1213
+ id: String((_a2 = node.id) != null ? _a2 : ""),
1214
+ name: String((_b2 = node.name) != null ? _b2 : ""),
1215
+ type: Number(node.type) || 0,
1216
+ parent: node.parent ? String(node.parent) : void 0,
1217
+ childs: Array.isArray(node.childs) ? node.childs.map(String) : [],
1218
+ modelFileId: node.modelFileId ? String(node.modelFileId) : void 0,
1219
+ persistentId: node.persistentId ? String(node.persistentId) : void 0,
1220
+ categoryName: node.categoryName ? String(node.categoryName) : void 0,
1221
+ familyName: node.familyName ? String(node.familyName) : void 0,
1222
+ typeName: node.typeName ? String(node.typeName) : void 0,
1223
+ isRealNode: Boolean(node.isRealNode)
1224
+ };
1225
+ }),
1226
+ rootNodeIds: Array.isArray(payload.rootNodeIds) ? payload.rootNodeIds.map(String) : void 0,
1227
+ timestamp: Number(payload.timestamp) || Date.now()
1228
+ });
1229
+ break;
1230
+ }
1134
1231
  case "viewer-sheets-list" /* SHEETS_LIST */: {
1135
1232
  const payload = data.payload;
1136
1233
  if (!payload || !payload.requestId || !Array.isArray(payload.sheets)) break;
package/dist/index.mjs CHANGED
@@ -54,11 +54,8 @@ var InteractionModule = class {
54
54
  panChange: (cb) => this.viewer._on("interaction:pan-change", cb)
55
55
  };
56
56
  }
57
- enablePan() {
58
- this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */, { enabled: true });
59
- }
60
- disablePan() {
61
- this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */, { enabled: false });
57
+ pan() {
58
+ this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */);
62
59
  }
63
60
  select() {
64
61
  this.viewer.postToViewer("viewer-select" /* SELECT */);
@@ -119,8 +116,8 @@ var NodeModule = class {
119
116
  };
120
117
 
121
118
  // src/modules/files.module.ts
122
- var DEFAULT_API_BASE_URL = "https://dev.3dviewer.anybim.vn/service/conversion";
123
- var DEFAULT_VIEWER_ORIGIN = "http://localhost:3000";
119
+ var DEFAULT_CONVERSION_URL = "https://dev.3dviewer.anybim.vn/service/conversion";
120
+ var DEFAULT_VIEWER_BASE_URL = "https://dev.3dviewer.anybim.vn";
124
121
  var SDK_VIEWER_PATH = "/mainviewer-sdk";
125
122
  var LEGACY_VIEWER_PATH = "/mainviewer";
126
123
  var FilesModule = class {
@@ -267,9 +264,9 @@ var FilesModule = class {
267
264
  normalizeBaseUrl(input) {
268
265
  return input.trim().replace(/\/+$/, "");
269
266
  }
270
- // Resolve API base URL with default fallback.
271
- resolveBaseUrl() {
272
- const raw = this.config.baseUrl || this.viewer.getOptions().baseUrl || DEFAULT_API_BASE_URL;
267
+ // Resolve conversion API URL with default fallback.
268
+ resolveConversionUrl() {
269
+ const raw = this.config.conversionUrl || this.viewer.getOptions().conversionUrl || DEFAULT_CONVERSION_URL;
273
270
  return this.normalizeBaseUrl(raw);
274
271
  }
275
272
  // Resolve viewer route path for all SDK flows.
@@ -295,26 +292,26 @@ var FilesModule = class {
295
292
  }
296
293
  // Viewer host used to open iframe after conversion completes.
297
294
  resolveViewerOrigin() {
298
- const viewerUrl = this.viewer.getOptions().url;
299
- if (viewerUrl) {
295
+ const configuredViewerBaseUrl = this.config.baseUrl || this.viewer.getOptions().baseUrl;
296
+ if (configuredViewerBaseUrl) {
300
297
  try {
301
- return this.normalizeBaseUrl(new URL(viewerUrl, window.location.href).origin);
298
+ return this.normalizeBaseUrl(new URL(configuredViewerBaseUrl, window.location.href).origin);
302
299
  } catch {
303
300
  }
304
301
  }
305
- const configuredBaseUrl = this.config.baseUrl || this.viewer.getOptions().baseUrl;
306
- if (configuredBaseUrl) {
302
+ const viewerUrl = this.viewer.getOptions().url;
303
+ if (viewerUrl) {
307
304
  try {
308
- return this.normalizeBaseUrl(new URL(configuredBaseUrl, window.location.href).origin);
305
+ return this.normalizeBaseUrl(new URL(viewerUrl, window.location.href).origin);
309
306
  } catch {
310
307
  }
311
308
  }
312
- return this.normalizeBaseUrl(DEFAULT_VIEWER_ORIGIN);
309
+ return this.normalizeBaseUrl(DEFAULT_VIEWER_BASE_URL);
313
310
  }
314
- // Resolve conversion service root from configured API base URL.
311
+ // Resolve conversion service root from configured conversion URL.
315
312
  // Do not auto-append path segments (e.g. "/service/conversion").
316
313
  resolveHostConversion() {
317
- return this.resolveBaseUrl();
314
+ return this.resolveConversionUrl();
318
315
  }
319
316
  // Resolve upload path sent to conversion APIs.
320
317
  getUploadPath() {
@@ -865,28 +862,56 @@ var ToolbarModule = class {
865
862
  };
866
863
 
867
864
  // src/modules/model-tree.module.ts
868
- function createRequestId2() {
869
- return `tree_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
865
+ function createRequestId2(prefix) {
866
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
867
+ }
868
+ function buildTree(nodes, rootNodeIds) {
869
+ const uniqueNodes = Array.from(
870
+ nodes.reduce((map, node) => {
871
+ map.set(node.id, node);
872
+ return map;
873
+ }, /* @__PURE__ */ new Map()),
874
+ ([, node]) => node
875
+ );
876
+ const hierarchyMap = new Map(
877
+ uniqueNodes.map((node) => [node.id, { ...node, children: [] }])
878
+ );
879
+ const childrenByParent = /* @__PURE__ */ new Map();
880
+ uniqueNodes.forEach((node) => {
881
+ var _a;
882
+ if (!node.parent) return;
883
+ const current = (_a = childrenByParent.get(node.parent)) != null ? _a : [];
884
+ current.push(node.id);
885
+ childrenByParent.set(node.parent, current);
886
+ });
887
+ hierarchyMap.forEach((node) => {
888
+ var _a, _b;
889
+ const childIdsFromNode = ((_a = node.childs) != null ? _a : []).filter((childId) => hierarchyMap.has(childId));
890
+ const fallbackChildIds = (_b = childrenByParent.get(node.id)) != null ? _b : [];
891
+ const childIds = childIdsFromNode.length > 0 ? childIdsFromNode : fallbackChildIds;
892
+ node.children = Array.from(new Set(childIds)).map((childId) => hierarchyMap.get(childId)).filter((child) => Boolean(child));
893
+ });
894
+ const rootIds = Array.isArray(rootNodeIds) && rootNodeIds.length > 0 ? rootNodeIds.filter((id) => hierarchyMap.has(id)) : uniqueNodes.filter((node) => !node.parent || !hierarchyMap.has(node.parent)).map((node) => node.id);
895
+ return Array.from(new Set(rootIds)).map((id) => hierarchyMap.get(id)).filter((node) => Boolean(node));
870
896
  }
871
897
  var ModelTreeModule = class {
872
898
  constructor(viewer) {
873
899
  this.viewer = viewer;
874
900
  }
875
901
  open() {
876
- this.viewer.postToViewer("viewer-panel-open" /* PANEL_OPEN */, {
902
+ this.postPanelOpen({
877
903
  panel: "model-tree",
878
904
  format: "3d"
879
905
  });
880
906
  }
881
907
  selectNode(nodeId) {
882
- this.viewer.postToViewer("viewer-tree-select-node" /* TREE_SELECT_NODE */, {
908
+ this.postTreeSelectNode({
883
909
  nodeId: String(nodeId)
884
910
  });
885
911
  }
886
912
  getNodeIds(options) {
887
- var _a;
888
- const requestId = createRequestId2();
889
- const timeoutMs = Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
913
+ const requestId = createRequestId2("tree");
914
+ const timeoutMs = this.resolveTimeoutMs(options);
890
915
  return new Promise((resolve, reject) => {
891
916
  const timer = setTimeout(() => {
892
917
  off();
@@ -898,12 +923,58 @@ var ModelTreeModule = class {
898
923
  off();
899
924
  resolve(payload.nodeIds);
900
925
  });
901
- this.viewer.postToViewer("viewer-tree-get-node-ids" /* TREE_GET_NODE_IDS */, {
926
+ this.postTreeGetNodeIds({
902
927
  requestId,
903
928
  onlyRealNodes: (options == null ? void 0 : options.onlyRealNodes) !== false
904
929
  });
905
930
  });
906
931
  }
932
+ getNodes(options) {
933
+ return this.requestNodes(options).then((response) => response.nodes);
934
+ }
935
+ getTree(options) {
936
+ return this.requestNodes(options).then((response) => buildTree(response.nodes, response.rootNodeIds));
937
+ }
938
+ requestNodes(options) {
939
+ const requestId = createRequestId2("tree_nodes");
940
+ const timeoutMs = this.resolveTimeoutMs(options);
941
+ return new Promise((resolve, reject) => {
942
+ const timer = setTimeout(() => {
943
+ off();
944
+ reject(new Error("Timeout while getting model tree nodes from viewer"));
945
+ }, timeoutMs);
946
+ const off = this.viewer._on("modelTree:nodes", (payload) => {
947
+ if (payload.requestId !== requestId) return;
948
+ clearTimeout(timer);
949
+ off();
950
+ resolve({
951
+ nodes: payload.nodes,
952
+ rootNodeIds: payload.rootNodeIds
953
+ });
954
+ });
955
+ this.postTreeGetNodes({
956
+ requestId,
957
+ // For backward compatibility, default includes helper/system nodes unless caller opts in.
958
+ onlyRealNodes: (options == null ? void 0 : options.onlyRealNodes) === true
959
+ });
960
+ });
961
+ }
962
+ resolveTimeoutMs(options) {
963
+ var _a;
964
+ return Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
965
+ }
966
+ postPanelOpen(payload) {
967
+ this.viewer.postToViewer("viewer-panel-open" /* PANEL_OPEN */, payload);
968
+ }
969
+ postTreeSelectNode(payload) {
970
+ this.viewer.postToViewer("viewer-tree-select-node" /* TREE_SELECT_NODE */, payload);
971
+ }
972
+ postTreeGetNodeIds(payload) {
973
+ this.viewer.postToViewer("viewer-tree-get-node-ids" /* TREE_GET_NODE_IDS */, payload);
974
+ }
975
+ postTreeGetNodes(payload) {
976
+ this.viewer.postToViewer("viewer-tree-get-nodes" /* TREE_GET_NODES */, payload);
977
+ }
907
978
  };
908
979
 
909
980
  // src/modules/markup.module.ts
@@ -1105,6 +1176,32 @@ var Viewer3D = class {
1105
1176
  });
1106
1177
  break;
1107
1178
  }
1179
+ case "viewer-tree-nodes" /* TREE_NODES */: {
1180
+ const payload = data.payload;
1181
+ if (!payload || !payload.requestId || !Array.isArray(payload.nodes)) break;
1182
+ this._emit("modelTree:nodes", {
1183
+ requestId: String(payload.requestId),
1184
+ nodes: payload.nodes.filter((node) => node && typeof node === "object").map((node) => {
1185
+ var _a2, _b2;
1186
+ return {
1187
+ id: String((_a2 = node.id) != null ? _a2 : ""),
1188
+ name: String((_b2 = node.name) != null ? _b2 : ""),
1189
+ type: Number(node.type) || 0,
1190
+ parent: node.parent ? String(node.parent) : void 0,
1191
+ childs: Array.isArray(node.childs) ? node.childs.map(String) : [],
1192
+ modelFileId: node.modelFileId ? String(node.modelFileId) : void 0,
1193
+ persistentId: node.persistentId ? String(node.persistentId) : void 0,
1194
+ categoryName: node.categoryName ? String(node.categoryName) : void 0,
1195
+ familyName: node.familyName ? String(node.familyName) : void 0,
1196
+ typeName: node.typeName ? String(node.typeName) : void 0,
1197
+ isRealNode: Boolean(node.isRealNode)
1198
+ };
1199
+ }),
1200
+ rootNodeIds: Array.isArray(payload.rootNodeIds) ? payload.rootNodeIds.map(String) : void 0,
1201
+ timestamp: Number(payload.timestamp) || Date.now()
1202
+ });
1203
+ break;
1204
+ }
1108
1205
  case "viewer-sheets-list" /* SHEETS_LIST */: {
1109
1206
  const payload = data.payload;
1110
1207
  if (!payload || !payload.requestId || !Array.isArray(payload.sheets)) break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "3dviewer-sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [