fl-web-component 2.0.8 → 2.0.9

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.
Files changed (29) hide show
  1. package/README.md +2 -0
  2. package/dist/fl-web-component.common.js +7198 -1250
  3. package/dist/fl-web-component.common.js.map +1 -1
  4. package/dist/fl-web-component.css +1 -1
  5. package/package.json +1 -1
  6. package/packages/components/com-graphics/component/context.js +123 -0
  7. package/packages/components/com-graphics/index.vue +1148 -69
  8. package/packages/utils/StreamLoader.js +73 -16
  9. package/src/utils/threejs/editor/command.js +36 -0
  10. package/src/utils/threejs/editor/commands/add-element-command.js +41 -0
  11. package/src/utils/threejs/editor/commands/add-group-command.js +10 -0
  12. package/src/utils/threejs/editor/commands/clone-element-command.js +100 -0
  13. package/src/utils/threejs/editor/commands/move-element-command.js +57 -0
  14. package/src/utils/threejs/editor/commands/multi-command.js +29 -0
  15. package/src/utils/threejs/editor/commands/remove-element-command.js +46 -0
  16. package/src/utils/threejs/editor/commands/reset-original-model-style-command.js +30 -0
  17. package/src/utils/threejs/editor/commands/set-geometry-params-command.js +41 -0
  18. package/src/utils/threejs/editor/commands/set-original-model-style-command.js +56 -0
  19. package/src/utils/threejs/editor/commands/set-position-command.js +54 -0
  20. package/src/utils/threejs/editor/commands/set-rotation-command.js +53 -0
  21. package/src/utils/threejs/editor/commands/set-scale-command.js +54 -0
  22. package/src/utils/threejs/editor/commands/set-value-command.js +107 -0
  23. package/src/utils/threejs/editor/constants.js +107 -0
  24. package/src/utils/threejs/editor/element-factory.js +163 -0
  25. package/src/utils/threejs/editor/event-bus.js +34 -0
  26. package/src/utils/threejs/editor/history.js +80 -0
  27. package/src/utils/threejs/editor/scene-command-service.js +1529 -0
  28. package/src/utils/threejs/editor/scene-event-bridge.js +32 -0
  29. package/src/utils/threejs/editor/scene-helpers.js +415 -0
@@ -0,0 +1,1529 @@
1
+ import { COMMAND_TYPE, EDITOR_EVENT, SCENE_NODE_TYPE, TRANSFORM_MODE } from './constants';
2
+ import SceneEventBus from './event-bus';
3
+ import History from './history';
4
+ import SceneEventBridge from './scene-event-bridge';
5
+ import {
6
+ buildObjectSnapshot,
7
+ buildCustomSaveTree,
8
+ buildCustomTree,
9
+ buildTreeSnapshot,
10
+ clampInsertIndex,
11
+ createDefaultElementName,
12
+ createDefaultGroupName,
13
+ disposeObjectResources,
14
+ ensureCustomRoot,
15
+ findSelectableSceneObject,
16
+ getSceneNodeType,
17
+ getObjectByUuid,
18
+ getObjectIndex,
19
+ insertObjectAt,
20
+ isAncestor,
21
+ isCloneableObject,
22
+ isCustomElement,
23
+ isCustomGroup,
24
+ isCustomRoot,
25
+ isDeletableObject,
26
+ isEditableObject,
27
+ isOriginalModelObject,
28
+ isObjectVisible,
29
+ isSelectableObject,
30
+ } from './scene-helpers';
31
+ import {
32
+ createElementByType,
33
+ normalizeGeometryParams,
34
+ createGroupElement,
35
+ } from './element-factory';
36
+ import AddElementCommand from './commands/add-element-command';
37
+ import AddGroupCommand from './commands/add-group-command';
38
+ import RemoveElementCommand from './commands/remove-element-command';
39
+ import CloneElementCommand from './commands/clone-element-command';
40
+ import MoveElementCommand from './commands/move-element-command';
41
+ import SetPositionCommand from './commands/set-position-command';
42
+ import SetRotationCommand from './commands/set-rotation-command';
43
+ import SetScaleCommand from './commands/set-scale-command';
44
+ import SetValueCommand from './commands/set-value-command';
45
+ import SetGeometryParamsCommand from './commands/set-geometry-params-command';
46
+ import SetOriginalModelStyleCommand from './commands/set-original-model-style-command';
47
+ import ResetOriginalModelStyleCommand from './commands/reset-original-model-style-command';
48
+ import MultiCommand from './commands/multi-command';
49
+
50
+ export default class SceneCommandService {
51
+ constructor(options = {}) {
52
+ this.THREE = options.THREE;
53
+ this.getScene = options.getScene;
54
+ this.requestRender = options.requestRender || function () {};
55
+ this.selectedUuid = '';
56
+ this.selectedNodeType = SCENE_NODE_TYPE.UNKNOWN;
57
+ this.transformMode = TRANSFORM_MODE.TRANSLATE;
58
+ this.objectLoader = new this.THREE.ObjectLoader();
59
+ this.eventBus = new SceneEventBus();
60
+ this.history = new History(this, options.historyOptions);
61
+ this.originalModelBaselines = new Map();
62
+ this.originalModelStyleChanges = new Map();
63
+ this.originalModelNodeNames = new Map();
64
+ this.eventBridge = new SceneEventBridge({
65
+ eventBus: this.eventBus,
66
+ getTreeSnapshot: () => this.getCustomTree(),
67
+ getOriginalModelStyleChanges: () => this.getOriginalModelStyleChanges(),
68
+ });
69
+ this.ensureReady();
70
+ }
71
+
72
+ ensureReady() {
73
+ const root = ensureCustomRoot(this.getSceneInstance(), this.THREE);
74
+ this.normalizeCustomObjectFlags(root);
75
+ this.normalizeCustomElementGeometryParams(root);
76
+ return root;
77
+ }
78
+
79
+ destroy() {
80
+ this.history.clear();
81
+ this.eventBridge.destroy();
82
+ this.selectedUuid = '';
83
+ this.selectedNodeType = SCENE_NODE_TYPE.UNKNOWN;
84
+ this.originalModelBaselines.clear();
85
+ this.originalModelStyleChanges.clear();
86
+ this.originalModelNodeNames.clear();
87
+ }
88
+
89
+ getSceneInstance() {
90
+ return typeof this.getScene === 'function' ? this.getScene() : null;
91
+ }
92
+
93
+ getCustomRoot() {
94
+ const root = ensureCustomRoot(this.getSceneInstance(), this.THREE);
95
+ this.normalizeCustomObjectFlags(root);
96
+ this.normalizeCustomElementGeometryParams(root);
97
+ return root;
98
+ }
99
+
100
+ normalizeCustomObjectFlags(root) {
101
+ const targetRoot = root || ensureCustomRoot(this.getSceneInstance(), this.THREE);
102
+ if (!targetRoot) return;
103
+ targetRoot.traverse(object => {
104
+ if (!object.userData) return;
105
+ if (object.userData.nodeType === 'custom-root') {
106
+ object.userData.rootType = 'custom-root';
107
+ return;
108
+ }
109
+ if (
110
+ (object.userData.nodeType === 'custom-element' ||
111
+ object.userData.nodeType === 'custom-group') &&
112
+ object.userData.rootType === 'custom-root'
113
+ ) {
114
+ delete object.userData.rootType;
115
+ }
116
+ });
117
+ }
118
+
119
+ normalizeCustomElementGeometryParams(root) {
120
+ const targetRoot = root || ensureCustomRoot(this.getSceneInstance(), this.THREE);
121
+ if (!targetRoot) return;
122
+ targetRoot.traverse(object => {
123
+ if (!isCustomElement(object)) return;
124
+ const elementType = object.userData ? object.userData.elementType : '';
125
+ if (!elementType) return;
126
+ object.userData.geometryParams = normalizeGeometryParams(
127
+ elementType,
128
+ object.userData.geometryParams || {}
129
+ );
130
+ });
131
+ }
132
+
133
+ parseObjectJSON(json) {
134
+ return this.objectLoader.parse(json);
135
+ }
136
+
137
+ subscribe(eventName, handler) {
138
+ return this.eventBus.on(eventName, handler);
139
+ }
140
+
141
+ emitEvent(eventName, payload) {
142
+ this.eventBridge.emit(eventName, payload);
143
+ }
144
+
145
+ getObjectByUuid(uuid) {
146
+ return getObjectByUuid(this.getSceneInstance(), uuid);
147
+ }
148
+
149
+ getSceneObjectDepth(object) {
150
+ let depth = 0;
151
+ let current = object;
152
+ while (current && current.parent) {
153
+ depth += 1;
154
+ current = current.parent;
155
+ }
156
+ return depth;
157
+ }
158
+
159
+ getOriginalModelResolvePriority(object) {
160
+ if (!object || !isOriginalModelObject(object)) return -1;
161
+ let priority = 0;
162
+ if (object.isInstancedMesh === true) {
163
+ priority += 8;
164
+ }
165
+ if (object.type && object.type !== 'Group') {
166
+ priority += 4;
167
+ }
168
+ if (this.getObjectMaterials(object).length > 0) {
169
+ priority += 2;
170
+ }
171
+ return priority;
172
+ }
173
+
174
+ compareOriginalModelResolveTargets(left, right) {
175
+ const leftPriority = this.getOriginalModelResolvePriority(left);
176
+ const rightPriority = this.getOriginalModelResolvePriority(right);
177
+ if (leftPriority !== rightPriority) {
178
+ return leftPriority - rightPriority;
179
+ }
180
+ return this.getSceneObjectDepth(left) - this.getSceneObjectDepth(right);
181
+ }
182
+
183
+ findOriginalModelObjectByName(name) {
184
+ const scene = this.getSceneInstance();
185
+ if (!scene || !name) return null;
186
+ const candidates = [];
187
+ scene.traverse(object => {
188
+ if (!isOriginalModelObject(object)) return;
189
+ if ((object.name || '') === name) {
190
+ candidates.push(object);
191
+ }
192
+ });
193
+ if (candidates.length === 0) {
194
+ return null;
195
+ }
196
+ candidates.sort((left, right) => this.compareOriginalModelResolveTargets(right, left));
197
+ return candidates[0] || null;
198
+ }
199
+
200
+ resolveOriginalModelObject(locator = {}) {
201
+ if (!locator || typeof locator !== 'object') return null;
202
+ const { uuid = '', name = '' } = locator;
203
+ const objectByUuid = uuid ? this.getObjectByUuid(uuid) : null;
204
+ const objectByName = name ? this.findOriginalModelObjectByName(name) : null;
205
+ if (objectByUuid && isOriginalModelObject(objectByUuid)) {
206
+ if (objectByName && this.compareOriginalModelResolveTargets(objectByName, objectByUuid) > 0) {
207
+ return objectByName;
208
+ }
209
+ return objectByUuid;
210
+ }
211
+ return objectByName;
212
+ }
213
+
214
+ getOriginalModelChangeLocatorKey(locator = {}) {
215
+ if (!locator || typeof locator !== 'object') return '';
216
+ const name = typeof locator.name === 'string' ? locator.name : '';
217
+ const uuid = typeof locator.uuid === 'string' ? locator.uuid : '';
218
+ return name || uuid;
219
+ }
220
+
221
+ setOriginalModelNodeName(locator = {}) {
222
+ if (!locator || typeof locator !== 'object') return '';
223
+ const nodeName = locator.nodeName !== undefined && locator.nodeName !== null
224
+ ? String(locator.nodeName)
225
+ : '';
226
+ const keys = [locator.uuid, locator.name]
227
+ .filter(key => key !== undefined && key !== null && String(key));
228
+ if (keys.length === 0) return '';
229
+ keys.forEach(key => {
230
+ if (nodeName) {
231
+ this.originalModelNodeNames.set(String(key), nodeName);
232
+ } else {
233
+ this.originalModelNodeNames.delete(String(key));
234
+ }
235
+ });
236
+ if (nodeName) {
237
+ const record = this.findOriginalModelStoredRecord(this.originalModelStyleChanges, locator);
238
+ if (record) {
239
+ record.nodeName = nodeName;
240
+ this.eventBridge.emitOriginalModelChangeListChanged();
241
+ }
242
+ }
243
+ return nodeName;
244
+ }
245
+
246
+ getOriginalModelNodeName(locator = {}) {
247
+ if (!locator || typeof locator !== 'object') return '';
248
+ const keys = [locator.uuid, locator.name]
249
+ .filter(key => key !== undefined && key !== null && String(key));
250
+ for (let index = 0; index < keys.length; index += 1) {
251
+ const nodeName = this.originalModelNodeNames.get(String(keys[index]));
252
+ if (nodeName) return nodeName;
253
+ }
254
+ return locator.nodeName || '';
255
+ }
256
+
257
+ cloneOriginalModelStyle(style = {}) {
258
+ return this.mergeOriginalModelStyle({}, style || {});
259
+ }
260
+
261
+ isSameOriginalModelStyle(left = {}, right = {}) {
262
+ return ['color', 'opacity', 'visible'].every(key => left[key] === right[key]);
263
+ }
264
+
265
+ buildOriginalModelStyleRecord(record = {}, options = {}) {
266
+ const styleSource = this.getOriginalModelStyleSource(record);
267
+ if (!record || !styleSource) return null;
268
+ const key = this.getOriginalModelChangeLocatorKey(record);
269
+ if (!key) return null;
270
+ const changedKeys =
271
+ Array.isArray(record.changedKeys) && record.changedKeys.length > 0
272
+ ? record.changedKeys.slice()
273
+ : ['color', 'opacity', 'visible'].filter(styleKey =>
274
+ Object.prototype.hasOwnProperty.call(styleSource, styleKey)
275
+ );
276
+ return {
277
+ uuid: record.uuid || '',
278
+ name: record.name || '',
279
+ nodeName: this.getOriginalModelNodeName(record),
280
+ nodeType: record.nodeType || SCENE_NODE_TYPE.ORIGINAL_MODEL,
281
+ changedKeys,
282
+ before: record.before ? this.cloneOriginalModelStyle(record.before) : null,
283
+ after: this.cloneOriginalModelStyle(styleSource),
284
+ updatedAt: Number.isFinite(options.updatedAt)
285
+ ? Number(options.updatedAt)
286
+ : Number.isFinite(record.updatedAt)
287
+ ? Number(record.updatedAt)
288
+ : Date.now(),
289
+ };
290
+ }
291
+
292
+ getOriginalModelStyleSource(record = {}) {
293
+ if (!record || typeof record !== 'object') return null;
294
+ if (record.after && typeof record.after === 'object') {
295
+ return record.after;
296
+ }
297
+ if (record.style && typeof record.style === 'object') {
298
+ return record.style;
299
+ }
300
+ const flatStyle = {};
301
+ ['color', 'opacity', 'visible'].forEach(key => {
302
+ if (Object.prototype.hasOwnProperty.call(record, key)) {
303
+ flatStyle[key] = record[key];
304
+ }
305
+ });
306
+ return Object.keys(flatStyle).length > 0 ? flatStyle : null;
307
+ }
308
+
309
+ normalizeCustomSaveNode(node) {
310
+ if (!node || typeof node !== 'object') return node;
311
+ const nextNode = {
312
+ ...node,
313
+ children: Array.isArray(node.children)
314
+ ? node.children.map(child => this.normalizeCustomSaveNode(child))
315
+ : [],
316
+ };
317
+ if (
318
+ !Object.prototype.hasOwnProperty.call(nextNode, 'material') &&
319
+ (Object.prototype.hasOwnProperty.call(nextNode, 'color') ||
320
+ Object.prototype.hasOwnProperty.call(nextNode, 'opacity'))
321
+ ) {
322
+ nextNode.material = {};
323
+ if (Object.prototype.hasOwnProperty.call(nextNode, 'color')) {
324
+ nextNode.material.color = nextNode.color;
325
+ }
326
+ if (Object.prototype.hasOwnProperty.call(nextNode, 'opacity')) {
327
+ nextNode.material.opacity = nextNode.opacity;
328
+ }
329
+ }
330
+ return nextNode;
331
+ }
332
+
333
+ normalizeOriginalModelSaveItem(item = {}) {
334
+ if (!item || typeof item !== 'object') return item;
335
+ const nextItem = { ...item };
336
+ if (!nextItem.style || typeof nextItem.style !== 'object') {
337
+ nextItem.style = {};
338
+ ['color', 'opacity', 'visible'].forEach(key => {
339
+ if (Object.prototype.hasOwnProperty.call(item, key)) {
340
+ nextItem.style[key] = item[key];
341
+ }
342
+ });
343
+ }
344
+ return nextItem;
345
+ }
346
+
347
+ normalizeSaveSnapshot(snapshot = {}) {
348
+ const payload = snapshot && typeof snapshot === 'object' ? snapshot : {};
349
+ const nextSnapshot = { ...payload };
350
+ if (Object.prototype.hasOwnProperty.call(payload, 'customModelTree')) {
351
+ nextSnapshot.customModelTree = this.normalizeCustomSaveNode(payload.customModelTree);
352
+ }
353
+ if (Object.prototype.hasOwnProperty.call(payload, 'originalModelList')) {
354
+ nextSnapshot.originalModelList = Array.isArray(payload.originalModelList)
355
+ ? payload.originalModelList.map(item => this.normalizeOriginalModelSaveItem(item))
356
+ : [];
357
+ }
358
+ return nextSnapshot;
359
+ }
360
+
361
+ findOriginalModelStoredRecord(map, locator = {}) {
362
+ if (!(map instanceof Map)) return null;
363
+ const key = this.getOriginalModelChangeLocatorKey(locator);
364
+ if (key && map.has(key)) {
365
+ return map.get(key);
366
+ }
367
+ const { uuid = '', name = '' } = locator || {};
368
+ let matchedRecord = null;
369
+ map.forEach((record, recordKey) => {
370
+ if (matchedRecord) return;
371
+ if (uuid && (recordKey === uuid || (record && record.uuid === uuid))) {
372
+ matchedRecord = record;
373
+ return;
374
+ }
375
+ if (name && record && record.name === name) {
376
+ matchedRecord = record;
377
+ }
378
+ });
379
+ return matchedRecord;
380
+ }
381
+
382
+ deleteOriginalModelStoredRecord(map, locator = {}, excludeKey = '') {
383
+ if (!(map instanceof Map)) return;
384
+ const key = this.getOriginalModelChangeLocatorKey(locator);
385
+ const { uuid = '', name = '' } = locator || {};
386
+ Array.from(map.entries()).forEach(([recordKey, record]) => {
387
+ if (excludeKey && recordKey === excludeKey) return;
388
+ const matchByKey = !!key && recordKey === key;
389
+ const matchByUuid = !!uuid && record && record.uuid === uuid;
390
+ const matchByName = !!name && record && record.name === name;
391
+ if (matchByKey || matchByUuid || matchByName) {
392
+ map.delete(recordKey);
393
+ }
394
+ });
395
+ }
396
+
397
+ getOriginalModelBaseline(locator = {}) {
398
+ const baseline = this.findOriginalModelStoredRecord(this.originalModelBaselines, locator);
399
+ return baseline ? JSON.parse(JSON.stringify(baseline)) : null;
400
+ }
401
+
402
+ setOriginalModelBaseline(locator = {}, baseline = {}) {
403
+ const key = this.getOriginalModelChangeLocatorKey(locator);
404
+ if (!key) return null;
405
+ const nextBaseline = {
406
+ color:
407
+ Object.prototype.hasOwnProperty.call(baseline, 'color') && baseline.color !== undefined
408
+ ? baseline.color
409
+ : null,
410
+ opacity:
411
+ Object.prototype.hasOwnProperty.call(baseline, 'opacity') && baseline.opacity !== undefined
412
+ ? Number(baseline.opacity)
413
+ : 1,
414
+ visible:
415
+ Object.prototype.hasOwnProperty.call(baseline, 'visible') && baseline.visible !== undefined
416
+ ? !!baseline.visible
417
+ : true,
418
+ name: locator.name || baseline.name || '',
419
+ uuid: locator.uuid || baseline.uuid || '',
420
+ nodeName: this.getOriginalModelNodeName(locator) || baseline.nodeName || '',
421
+ };
422
+ this.deleteOriginalModelStoredRecord(this.originalModelBaselines, locator, key);
423
+ this.originalModelBaselines.set(key, nextBaseline);
424
+ return JSON.parse(JSON.stringify(nextBaseline));
425
+ }
426
+
427
+ setOriginalModelStyleRecord(record = {}, options = {}) {
428
+ const nextRecord = this.buildOriginalModelStyleRecord(record, options);
429
+ if (!nextRecord) return null;
430
+ const key = this.getOriginalModelChangeLocatorKey(nextRecord);
431
+ if (nextRecord.nodeName) {
432
+ this.setOriginalModelNodeName(nextRecord);
433
+ }
434
+ this.deleteOriginalModelStoredRecord(this.originalModelStyleChanges, nextRecord, key);
435
+ this.originalModelStyleChanges.set(key, nextRecord);
436
+ return JSON.parse(JSON.stringify(nextRecord));
437
+ }
438
+
439
+ getOriginalModelStyleRecords() {
440
+ return Array.from(this.originalModelStyleChanges.values())
441
+ .sort((left, right) => right.updatedAt - left.updatedAt)
442
+ .map(item => JSON.parse(JSON.stringify(item)));
443
+ }
444
+
445
+ findSelectableObject(target) {
446
+ return findSelectableSceneObject(target);
447
+ }
448
+
449
+ getNodeType(uuid) {
450
+ return getSceneNodeType(this.getObjectByUuid(uuid));
451
+ }
452
+
453
+ getSelectedNodeType() {
454
+ return this.selectedNodeType;
455
+ }
456
+
457
+ getObjectMaterials(object) {
458
+ if (!object || !object.material) return [];
459
+ return Array.isArray(object.material)
460
+ ? object.material.filter(Boolean)
461
+ : [object.material].filter(Boolean);
462
+ }
463
+
464
+ getOriginalModelStyleValue(uuid) {
465
+ const object = this.getObjectByUuid(uuid);
466
+ const materials = this.getObjectMaterials(object);
467
+ const primaryMaterial = materials[0] || null;
468
+ return {
469
+ color:
470
+ primaryMaterial && primaryMaterial.color && primaryMaterial.color.isColor
471
+ ? `#${primaryMaterial.color.getHexString()}`
472
+ : null,
473
+ opacity:
474
+ primaryMaterial && typeof primaryMaterial.opacity === 'number'
475
+ ? primaryMaterial.opacity
476
+ : 1,
477
+ visible: object ? object.visible !== false : true,
478
+ };
479
+ }
480
+
481
+ mergeOriginalModelStyle(baseStyle = {}, patchStyle = {}) {
482
+ return {
483
+ color:
484
+ patchStyle.color !== undefined
485
+ ? patchStyle.color
486
+ : Object.prototype.hasOwnProperty.call(baseStyle, 'color')
487
+ ? baseStyle.color
488
+ : null,
489
+ opacity:
490
+ patchStyle.opacity !== undefined
491
+ ? Number(patchStyle.opacity)
492
+ : Object.prototype.hasOwnProperty.call(baseStyle, 'opacity')
493
+ ? Number(baseStyle.opacity)
494
+ : 1,
495
+ visible:
496
+ patchStyle.visible !== undefined
497
+ ? !!patchStyle.visible
498
+ : Object.prototype.hasOwnProperty.call(baseStyle, 'visible')
499
+ ? !!baseStyle.visible
500
+ : true,
501
+ };
502
+ }
503
+
504
+ // buildOriginalModelPath(object) {
505
+ // const path = [];
506
+ // let current = object;
507
+ // while (current && current.parent) {
508
+ // path.unshift(current.name || current.type || current.uuid);
509
+ // current = current.parent;
510
+ // }
511
+ // return path;
512
+ // }
513
+
514
+ ensureOriginalModelBaseline(uuid) {
515
+ const object = this.getObjectByUuid(uuid);
516
+ if (!object || !isOriginalModelObject(object)) {
517
+ throw new Error('当前节点不是原始模型节点');
518
+ }
519
+ const locator = {
520
+ uuid,
521
+ name: object.name || object.type || '未命名节点',
522
+ };
523
+ const nodeName = this.getOriginalModelNodeName(locator);
524
+ const baseline = this.getOriginalModelBaseline(locator);
525
+ if (baseline) {
526
+ if (!this.originalModelBaselines.has(this.getOriginalModelChangeLocatorKey(locator))) {
527
+ this.setOriginalModelBaseline(locator, baseline);
528
+ }
529
+ return baseline;
530
+ }
531
+ return this.setOriginalModelBaseline(locator, {
532
+ ...this.getOriginalModelStyleValue(uuid),
533
+ name: locator.name,
534
+ uuid,
535
+ nodeName,
536
+ // path: this.buildOriginalModelPath(object),
537
+ });
538
+ }
539
+
540
+ applyOriginalModelStyle(uuid, style = {}) {
541
+ const object = this.getObjectByUuid(uuid);
542
+ if (!object || !isOriginalModelObject(object)) {
543
+ throw new Error('当前节点不是原始模型节点');
544
+ }
545
+ const nextStyle = this.mergeOriginalModelStyle(this.getOriginalModelStyleValue(uuid), style);
546
+ object.visible = nextStyle.visible !== false;
547
+ this.getObjectMaterials(object).forEach(material => {
548
+ if (nextStyle.color && material.color && material.color.isColor) {
549
+ material.color.set(nextStyle.color);
550
+ }
551
+ if (Number.isFinite(nextStyle.opacity)) {
552
+ material.opacity = Math.min(Math.max(Number(nextStyle.opacity), 0), 1);
553
+ material.transparent = material.opacity < 1;
554
+ }
555
+ material.needsUpdate = true;
556
+ });
557
+ object.updateMatrixWorld(true);
558
+ return this.getOriginalModelStyleValue(uuid);
559
+ }
560
+
561
+ getOriginalModelChangedKeys(baselineStyle = {}, currentStyle = {}) {
562
+ return ['color', 'opacity', 'visible'].filter(key => baselineStyle[key] !== currentStyle[key]);
563
+ }
564
+
565
+ // buildOriginalModelChangeSummary(name, changedKeys = []) {
566
+ // const labelMap = {
567
+ // color: '变色',
568
+ // opacity: '透明',
569
+ // visible: '隐藏',
570
+ // };
571
+ // return `${name || '未命名节点'}【${changedKeys.map(key => labelMap[key] || key).join('、')}】`;
572
+ // }
573
+
574
+ syncOriginalModelStyleRecord(uuid, options = {}) {
575
+ const object = this.getObjectByUuid(uuid);
576
+ if (!object || !isOriginalModelObject(object)) {
577
+ return null;
578
+ }
579
+ const baseline = this.ensureOriginalModelBaseline(uuid);
580
+ const currentStyle = this.getOriginalModelStyleValue(uuid);
581
+ const changedKeys = this.getOriginalModelChangedKeys(baseline, currentStyle);
582
+ const locator = {
583
+ uuid,
584
+ name: object.name || object.type || '未命名节点',
585
+ };
586
+ const nodeName = this.getOriginalModelNodeName(locator);
587
+ if (changedKeys.length === 0) {
588
+ this.deleteOriginalModelStoredRecord(this.originalModelStyleChanges, locator);
589
+ return null;
590
+ }
591
+ const updatedAt = Number.isFinite(options.updatedAt) ? Number(options.updatedAt) : Date.now();
592
+ return this.setOriginalModelStyleRecord(
593
+ {
594
+ uuid,
595
+ name: locator.name,
596
+ nodeName,
597
+ // path: baseline.path || this.buildOriginalModelPath(object),
598
+ changedKeys,
599
+ before: {
600
+ color: baseline.color,
601
+ opacity: baseline.opacity,
602
+ visible: baseline.visible,
603
+ },
604
+ after: currentStyle,
605
+ // summary: this.buildOriginalModelChangeSummary(object.name || object.type, changedKeys),
606
+ nodeType: SCENE_NODE_TYPE.ORIGINAL_MODEL,
607
+ },
608
+ {
609
+ updatedAt,
610
+ }
611
+ );
612
+ }
613
+
614
+ getOriginalModelStyle(locatorKey) {
615
+ if (!locatorKey) return null;
616
+ const object = this.getObjectByUuid(locatorKey);
617
+ const locator =
618
+ object && isOriginalModelObject(object)
619
+ ? {
620
+ uuid: locatorKey,
621
+ name: object.name || object.type || '未命名节点',
622
+ }
623
+ : {
624
+ uuid: locatorKey,
625
+ name: locatorKey,
626
+ };
627
+ const record = this.findOriginalModelStoredRecord(this.originalModelStyleChanges, locator);
628
+ return record ? JSON.parse(JSON.stringify(record)) : null;
629
+ }
630
+
631
+ getOriginalModelChangeRecord(locatorKey) {
632
+ return this.getOriginalModelStyle(locatorKey);
633
+ }
634
+
635
+ buildOriginalModelSnapshot(object) {
636
+ if (!object) return null;
637
+ const snapshot = buildObjectSnapshot(object);
638
+ if (!snapshot) return null;
639
+ const nodeName = this.getOriginalModelNodeName({
640
+ uuid: object.uuid,
641
+ name: object.name || object.type || '',
642
+ });
643
+ return {
644
+ ...snapshot,
645
+ nodeName: nodeName || snapshot.nodeName || '',
646
+ nodeNameReady: !!(nodeName || snapshot.nodeName),
647
+ nodeType: SCENE_NODE_TYPE.ORIGINAL_MODEL,
648
+ elementType: '',
649
+ geometryParams: null,
650
+ material: snapshot.material || {
651
+ color: null,
652
+ opacity: 1,
653
+ },
654
+ };
655
+ }
656
+
657
+ buildOriginalModelSelectionPayload(uuid) {
658
+ const object = this.getObjectByUuid(uuid);
659
+ if (!object || !isOriginalModelObject(object)) {
660
+ return {
661
+ uuid: '',
662
+ nodeType: SCENE_NODE_TYPE.UNKNOWN,
663
+ data: null,
664
+ changedKeys: [],
665
+ };
666
+ }
667
+ const currentRecord = this.getOriginalModelChangeRecord(uuid);
668
+ const nodeName = this.getOriginalModelNodeName({
669
+ uuid,
670
+ name: object.name || object.type || '',
671
+ });
672
+ return {
673
+ uuid,
674
+ nodeType: SCENE_NODE_TYPE.ORIGINAL_MODEL,
675
+ data: this.buildOriginalModelSnapshot(object),
676
+ nodeName,
677
+ nodeNameReady: !!nodeName,
678
+ changedKeys:
679
+ currentRecord && Array.isArray(currentRecord.changedKeys)
680
+ ? currentRecord.changedKeys.slice()
681
+ : [],
682
+ };
683
+ }
684
+
685
+ buildOriginalModelChangeListItem(record) {
686
+ if (!record) return null;
687
+ const recordKey =
688
+ record.uuid || record.name || `original-model-change-${record.updatedAt || 0}`;
689
+ return {
690
+ key: recordKey,
691
+ uuid: record.uuid || record.name || '',
692
+ name: record.name || '',
693
+ nodeName: record.nodeName || record.name || '',
694
+ nodeNameReady: true,
695
+ nodeType: SCENE_NODE_TYPE.ORIGINAL_MODEL,
696
+ changedKeys: Array.isArray(record.changedKeys) ? record.changedKeys.slice() : [],
697
+ };
698
+ }
699
+
700
+ getOriginalModelStyleChanges() {
701
+ return this.getOriginalModelStyleRecords()
702
+ .map(item => this.buildOriginalModelChangeListItem(item))
703
+ .filter(Boolean);
704
+ }
705
+
706
+ buildSelectionPayload(object) {
707
+ const nodeType = getSceneNodeType(object);
708
+ if (nodeType === SCENE_NODE_TYPE.ORIGINAL_MODEL) {
709
+ return this.buildOriginalModelSelectionPayload(object ? object.uuid : '');
710
+ }
711
+ return {
712
+ uuid: object ? object.uuid : '',
713
+ nodeType,
714
+ data: object ? buildObjectSnapshot(object) : null,
715
+ };
716
+ }
717
+
718
+ buildObjectChangedPayload(object, changedKeys = []) {
719
+ if (!object) {
720
+ return {
721
+ uuid: '',
722
+ nodeType: '',
723
+ data: null,
724
+ changedKeys: Array.isArray(changedKeys) ? changedKeys.slice() : [],
725
+ };
726
+ }
727
+ const snapshot = buildObjectSnapshot(object);
728
+ const nodeType =
729
+ snapshot && Object.prototype.hasOwnProperty.call(snapshot, 'nodeType')
730
+ ? snapshot.nodeType || ''
731
+ : '';
732
+ return {
733
+ uuid: object.uuid,
734
+ nodeType,
735
+ data: snapshot,
736
+ changedKeys: Array.isArray(changedKeys) ? changedKeys.slice() : [],
737
+ };
738
+ }
739
+
740
+ selectElement(uuid, options = {}) {
741
+ const object = this.getObjectByUuid(uuid);
742
+ if (
743
+ !object ||
744
+ !(isSelectableObject(object) || isOriginalModelObject(object))
745
+ // || !isObjectVisible(object)
746
+ ) {
747
+ this.clearSelection(options);
748
+ return null;
749
+ }
750
+ return this.selectObject(uuid, options);
751
+ }
752
+
753
+ selectObject(uuid, options = {}) {
754
+ const object = this.getObjectByUuid(uuid);
755
+ const nodeType = getSceneNodeType(object);
756
+ if (
757
+ object &&
758
+ // isObjectVisible(object) &&
759
+ (isSelectableObject(object) || isOriginalModelObject(object))
760
+ ) {
761
+ this.selectedUuid = uuid;
762
+ this.selectedNodeType = nodeType;
763
+ const selectionPayload = this.buildSelectionPayload(object);
764
+ if (options.emitEvent !== false) {
765
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_SELECTED, selectionPayload);
766
+ if (nodeType === SCENE_NODE_TYPE.ORIGINAL_MODEL) {
767
+ this.eventBridge.emit(EDITOR_EVENT.ORIGINAL_MODEL_SELECTED, selectionPayload);
768
+ }
769
+ }
770
+ return nodeType === SCENE_NODE_TYPE.ORIGINAL_MODEL ? selectionPayload : object;
771
+ }
772
+ this.clearSelection(options);
773
+ return null;
774
+ }
775
+
776
+ clearSelection(options = {}) {
777
+ const previousNodeType = this.selectedNodeType;
778
+ this.selectedUuid = '';
779
+ this.selectedNodeType = SCENE_NODE_TYPE.UNKNOWN;
780
+ if (options.emitEvent !== false) {
781
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_SELECTED, {
782
+ uuid: '',
783
+ nodeType: SCENE_NODE_TYPE.UNKNOWN,
784
+ data: null,
785
+ });
786
+ if (previousNodeType === SCENE_NODE_TYPE.ORIGINAL_MODEL) {
787
+ this.eventBridge.emit(EDITOR_EVENT.ORIGINAL_MODEL_SELECTED, {
788
+ uuid: '',
789
+ data: null,
790
+ changedKeys: [],
791
+ });
792
+ }
793
+ }
794
+ }
795
+
796
+ setTransformMode(mode, options = {}) {
797
+ const nextMode = Object.values(TRANSFORM_MODE).indexOf(mode) !== -1 ? mode : this.transformMode;
798
+ if (!nextMode) return this.transformMode;
799
+ const changed = nextMode !== this.transformMode;
800
+ this.transformMode = nextMode;
801
+ if ((changed || options.forceEmit === true) && options.emitEvent !== false) {
802
+ this.eventBridge.emit(EDITOR_EVENT.TRANSFORM_MODE_CHANGED, {
803
+ mode: this.transformMode,
804
+ });
805
+ }
806
+ return this.transformMode;
807
+ }
808
+
809
+ getTransformMode() {
810
+ return this.transformMode;
811
+ }
812
+
813
+ getObjectSnapshot(uuid) {
814
+ return buildObjectSnapshot(this.getObjectByUuid(uuid));
815
+ }
816
+
817
+ getSelectedObjectSnapshot() {
818
+ if (!this.selectedUuid) return null;
819
+ return this.getObjectSnapshot(this.selectedUuid);
820
+ }
821
+
822
+ getSelectedOriginalModelStyleSnapshot() {
823
+ if (!this.selectedUuid || this.selectedNodeType !== SCENE_NODE_TYPE.ORIGINAL_MODEL) {
824
+ return null;
825
+ }
826
+ return this.buildOriginalModelSelectionPayload(this.selectedUuid);
827
+ }
828
+
829
+ resolveInsertTarget(selectedUuid = this.selectedUuid) {
830
+ const root = this.getCustomRoot();
831
+ const selected = this.getObjectByUuid(selectedUuid);
832
+ if (!selected || !isSelectableObject(selected)) {
833
+ return {
834
+ parentUuid: root.uuid,
835
+ insertMode: 'append',
836
+ };
837
+ }
838
+
839
+ if (isCustomRoot(selected)) {
840
+ return {
841
+ parentUuid: selected.uuid,
842
+ insertMode: 'append',
843
+ };
844
+ }
845
+
846
+ if (isCustomGroup(selected)) {
847
+ return {
848
+ parentUuid: selected.uuid,
849
+ insertMode: 'append',
850
+ };
851
+ }
852
+
853
+ if (isCustomElement(selected)) {
854
+ return {
855
+ parentUuid: selected.parent ? selected.parent.uuid : root.uuid,
856
+ referenceUuid: selected.uuid,
857
+ insertMode: 'after',
858
+ };
859
+ }
860
+
861
+ return {
862
+ parentUuid: root.uuid,
863
+ insertMode: 'append',
864
+ };
865
+ }
866
+
867
+ resolveTargetParent(parentUuid) {
868
+ const root = this.getCustomRoot();
869
+ const parent = this.getObjectByUuid(parentUuid);
870
+ if (!parent) return root;
871
+ if (isCustomRoot(parent) || isCustomGroup(parent)) {
872
+ return parent;
873
+ }
874
+ return root;
875
+ }
876
+
877
+ resolveInsertIndex(parent, referenceUuid, insertMode = 'append') {
878
+ if (!parent) return 0;
879
+ if (!referenceUuid || insertMode === 'append') {
880
+ return parent.children.length;
881
+ }
882
+ const referenceObject = this.getObjectByUuid(referenceUuid);
883
+ if (!referenceObject || referenceObject.parent !== parent) {
884
+ return parent.children.length;
885
+ }
886
+ const referenceIndex = parent.children.indexOf(referenceObject);
887
+ if (referenceIndex === -1) {
888
+ return parent.children.length;
889
+ }
890
+ if (insertMode === 'before') {
891
+ return referenceIndex;
892
+ }
893
+ if (insertMode === 'after') {
894
+ return referenceIndex + 1;
895
+ }
896
+ return parent.children.length;
897
+ }
898
+
899
+ executeCommand(command, options = {}) {
900
+ const result = this.history.execute(command);
901
+ this.requestRender();
902
+ this.emitCommandEvents(result.command, options.action || 'execute', {
903
+ merged: result.merged,
904
+ transformMode: options.transformMode,
905
+ });
906
+ return result.command.getObject ? result.command.getObject() : null;
907
+ }
908
+
909
+ emitCommandEvents(command, action, options = {}) {
910
+ if (!command) return;
911
+
912
+ if (command.type === COMMAND_TYPE.MULTI) {
913
+ command.commands.forEach(childCommand => {
914
+ this.emitSingleCommandEvents(childCommand, action, {
915
+ ...options,
916
+ skipHistoryChanged: true,
917
+ });
918
+ });
919
+ this.emitHistoryChanged(action, command, options.merged);
920
+ return;
921
+ }
922
+
923
+ this.emitSingleCommandEvents(command, action, options);
924
+ }
925
+
926
+ emitSingleCommandEvents(command, action, options = {}) {
927
+ const object = command.getObject ? command.getObject() : null;
928
+ const parentUuid = object && object.parent ? object.parent.uuid : command.parentUuid || '';
929
+ if (
930
+ command.type === COMMAND_TYPE.ADD_ELEMENT ||
931
+ command.type === COMMAND_TYPE.ADD_GROUP ||
932
+ command.type === COMMAND_TYPE.CLONE_ELEMENT
933
+ ) {
934
+ if (action === 'execute' || action === 'redo') {
935
+ this.selectedUuid = object ? object.uuid : '';
936
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_ADDED, {
937
+ uuid: object ? object.uuid : '',
938
+ parentUuid,
939
+ nodeType: object && object.userData ? object.userData.nodeType : '',
940
+ elementType: object && object.userData ? object.userData.elementType : '',
941
+ });
942
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_SELECTED, {
943
+ uuid: object ? object.uuid : '',
944
+ nodeType: object ? getSceneNodeType(object) : SCENE_NODE_TYPE.UNKNOWN,
945
+ data: object ? this.getObjectSnapshot(object.uuid) : null,
946
+ });
947
+ this.eventBridge.emitSceneGraphChanged();
948
+ this.setTransformMode(options.transformMode || TRANSFORM_MODE.TRANSLATE, {
949
+ forceEmit: true,
950
+ });
951
+ } else {
952
+ if (this.selectedUuid === (object && object.uuid)) {
953
+ this.clearSelection();
954
+ }
955
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_REMOVED, {
956
+ uuid: object ? object.uuid : '',
957
+ parentUuid: command.parentUuid || '',
958
+ removedTree: buildTreeSnapshot(object),
959
+ });
960
+ this.eventBridge.emitSceneGraphChanged();
961
+ }
962
+ this.emitHistoryChanged(action, command, options.merged);
963
+ return;
964
+ }
965
+
966
+ if (command.type === COMMAND_TYPE.REMOVE_ELEMENT) {
967
+ if (action === 'execute' || action === 'redo') {
968
+ if (
969
+ object &&
970
+ this.selectedUuid &&
971
+ (this.selectedUuid === object.uuid ||
972
+ object.getObjectByProperty('uuid', this.selectedUuid))
973
+ ) {
974
+ this.clearSelection();
975
+ }
976
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_REMOVED, {
977
+ uuid: object ? object.uuid : '',
978
+ parentUuid: command.parentUuid || '',
979
+ removedTree: buildTreeSnapshot(object),
980
+ });
981
+ } else {
982
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_ADDED, {
983
+ uuid: object ? object.uuid : '',
984
+ parentUuid,
985
+ nodeType: object && object.userData ? object.userData.nodeType : '',
986
+ elementType: object && object.userData ? object.userData.elementType : '',
987
+ });
988
+ }
989
+ this.eventBridge.emitSceneGraphChanged();
990
+ this.emitHistoryChanged(action, command, options.merged);
991
+ return;
992
+ }
993
+
994
+ if (command.type === COMMAND_TYPE.MOVE_ELEMENT) {
995
+ this.eventBridge.emit(
996
+ EDITOR_EVENT.OBJECT_CHANGED,
997
+ this.buildObjectChangedPayload(object, ['parentUuid'])
998
+ );
999
+ this.eventBridge.emitSceneGraphChanged();
1000
+ this.emitHistoryChanged(action, command, options.merged);
1001
+ return;
1002
+ }
1003
+
1004
+ if (
1005
+ command.type === COMMAND_TYPE.SET_ORIGINAL_MODEL_STYLE ||
1006
+ command.type === COMMAND_TYPE.RESET_ORIGINAL_MODEL_STYLE
1007
+ ) {
1008
+ const currentObject = object || this.getObjectByUuid(command.uuid);
1009
+ const originalModelPayload =
1010
+ currentObject && currentObject.uuid
1011
+ ? this.buildOriginalModelSelectionPayload(currentObject.uuid)
1012
+ : {
1013
+ uuid: command.uuid || '',
1014
+ data: null,
1015
+ changedKeys: [],
1016
+ };
1017
+ const payload = {
1018
+ uuid: originalModelPayload.uuid,
1019
+ nodeType: SCENE_NODE_TYPE.ORIGINAL_MODEL,
1020
+ data: originalModelPayload.data,
1021
+ changedKeys: Array.isArray(originalModelPayload.changedKeys)
1022
+ ? originalModelPayload.changedKeys.slice()
1023
+ : [],
1024
+ };
1025
+ this.eventBridge.emit(EDITOR_EVENT.OBJECT_CHANGED, payload);
1026
+ if (command.type === COMMAND_TYPE.RESET_ORIGINAL_MODEL_STYLE) {
1027
+ this.eventBridge.emit(EDITOR_EVENT.ORIGINAL_MODEL_STYLE_RESET, payload);
1028
+ // name: currentObject ? currentObject.name || currentObject.type || '未命名节点' : '',
1029
+ } else {
1030
+ this.eventBridge.emit(EDITOR_EVENT.ORIGINAL_MODEL_STYLE_CHANGED, payload);
1031
+ }
1032
+ this.eventBridge.emitOriginalModelChangeListChanged();
1033
+ if (
1034
+ currentObject &&
1035
+ this.selectedUuid === currentObject.uuid &&
1036
+ this.selectedNodeType === SCENE_NODE_TYPE.ORIGINAL_MODEL
1037
+ ) {
1038
+ this.eventBridge.emit(
1039
+ EDITOR_EVENT.ORIGINAL_MODEL_SELECTED,
1040
+ this.buildOriginalModelSelectionPayload(currentObject.uuid)
1041
+ );
1042
+ }
1043
+ this.emitHistoryChanged(action, command, options.merged);
1044
+ return;
1045
+ }
1046
+
1047
+ this.eventBridge.emit(
1048
+ EDITOR_EVENT.OBJECT_CHANGED,
1049
+ this.buildObjectChangedPayload(object, command.getChangedKeys())
1050
+ );
1051
+ if (command.getChangedKeys().indexOf('name') !== -1) {
1052
+ this.eventBridge.emitSceneGraphChanged();
1053
+ }
1054
+ this.emitHistoryChanged(action, command, options.merged);
1055
+ }
1056
+
1057
+ emitHistoryChanged(action, command, merged) {
1058
+ this.eventBridge.emit(EDITOR_EVENT.HISTORY_CHANGED, {
1059
+ action,
1060
+ commandType: command.type,
1061
+ commandName: command.name,
1062
+ merged: !!merged,
1063
+ ...this.history.getState(),
1064
+ });
1065
+ }
1066
+
1067
+ addElement(options = {}) {
1068
+ this.ensureReady();
1069
+ const target =
1070
+ options.parentUuid || options.referenceUuid
1071
+ ? {
1072
+ parentUuid:
1073
+ options.parentUuid ||
1074
+ (() => {
1075
+ const referenceObject = this.getObjectByUuid(options.referenceUuid);
1076
+ return referenceObject && referenceObject.parent ? referenceObject.parent.uuid : '';
1077
+ })(),
1078
+ referenceUuid: options.referenceUuid,
1079
+ insertMode: options.insertMode || 'append',
1080
+ }
1081
+ : this.resolveInsertTarget(options.selectedUuid);
1082
+ const parent = this.resolveTargetParent(target.parentUuid);
1083
+ const object = createElementByType(this.THREE, options.type, parent, {
1084
+ name: options.name || createDefaultElementName(parent, options.type),
1085
+ transform: options.transform,
1086
+ geometryParams: options.geometryParams,
1087
+ });
1088
+ const index = this.resolveInsertIndex(parent, target.referenceUuid, target.insertMode);
1089
+ const command = new AddElementCommand(this, object, {
1090
+ parentUuid: parent.uuid,
1091
+ index,
1092
+ });
1093
+ return this.executeCommand(command, {
1094
+ transformMode: TRANSFORM_MODE.TRANSLATE,
1095
+ });
1096
+ }
1097
+
1098
+ addGroup(options = {}) {
1099
+ this.ensureReady();
1100
+ const target =
1101
+ options.parentUuid || options.referenceUuid
1102
+ ? {
1103
+ parentUuid:
1104
+ options.parentUuid ||
1105
+ (() => {
1106
+ const referenceObject = this.getObjectByUuid(options.referenceUuid);
1107
+ return referenceObject && referenceObject.parent ? referenceObject.parent.uuid : '';
1108
+ })(),
1109
+ referenceUuid: options.referenceUuid,
1110
+ insertMode: options.insertMode || 'append',
1111
+ }
1112
+ : this.resolveInsertTarget(options.selectedUuid);
1113
+ const parent = this.resolveTargetParent(target.parentUuid);
1114
+ const object = createGroupElement(this.THREE, parent, {
1115
+ name: options.name || createDefaultGroupName(parent),
1116
+ transform: options.transform,
1117
+ });
1118
+ const index = this.resolveInsertIndex(parent, target.referenceUuid, target.insertMode);
1119
+ const command = new AddGroupCommand(this, object, {
1120
+ parentUuid: parent.uuid,
1121
+ index,
1122
+ });
1123
+ return this.executeCommand(command, {
1124
+ transformMode: TRANSFORM_MODE.TRANSLATE,
1125
+ });
1126
+ }
1127
+
1128
+ removeElement(uuid) {
1129
+ const object = this.getObjectByUuid(uuid);
1130
+ if (!object || !isDeletableObject(object)) {
1131
+ throw new Error('当前节点不允许删除');
1132
+ }
1133
+ const command = new RemoveElementCommand(this, uuid);
1134
+ this.executeCommand(command);
1135
+ }
1136
+
1137
+ cloneElement(uuid, options = {}) {
1138
+ const source = this.getObjectByUuid(uuid);
1139
+ if (!source || !isCloneableObject(source)) {
1140
+ throw new Error('当前节点不允许复制');
1141
+ }
1142
+ const referenceObject = options.referenceUuid
1143
+ ? this.getObjectByUuid(options.referenceUuid)
1144
+ : null;
1145
+ const targetParent = this.resolveTargetParent(
1146
+ options.parentUuid ||
1147
+ (referenceObject && referenceObject.parent ? referenceObject.parent.uuid : '') ||
1148
+ (source.parent ? source.parent.uuid : '')
1149
+ );
1150
+ const index =
1151
+ typeof options.referenceUuid !== 'undefined' || typeof options.insertMode !== 'undefined'
1152
+ ? this.resolveInsertIndex(
1153
+ targetParent,
1154
+ options.referenceUuid,
1155
+ options.insertMode || 'append'
1156
+ )
1157
+ : clampInsertIndex(targetParent, getObjectIndex(source) + 1);
1158
+ const command = new CloneElementCommand(this, uuid, {
1159
+ parentUuid: targetParent.uuid,
1160
+ index,
1161
+ positionOffset: options.positionOffset,
1162
+ disableAutoOffset: options.disableAutoOffset,
1163
+ });
1164
+ return this.executeCommand(command, {
1165
+ transformMode: TRANSFORM_MODE.TRANSLATE,
1166
+ });
1167
+ }
1168
+
1169
+ moveElement(uuid, targetParentUuid, referenceUuid, insertMode = 'append') {
1170
+ const object = this.getObjectByUuid(uuid);
1171
+ if (!object || !isEditableObject(object)) {
1172
+ throw new Error('当前节点不允许移动');
1173
+ }
1174
+ const targetParent = this.resolveTargetParent(targetParentUuid);
1175
+ if (isAncestor(object, targetParent)) {
1176
+ throw new Error('不允许把节点移动到自己的后代节点下');
1177
+ }
1178
+ const index = this.resolveInsertIndex(targetParent, referenceUuid, insertMode);
1179
+ const command = new MoveElementCommand(this, uuid, {
1180
+ targetParentUuid: targetParent.uuid,
1181
+ index,
1182
+ });
1183
+ return this.executeCommand(command);
1184
+ }
1185
+
1186
+ updateElement(options = {}) {
1187
+ const object = this.getObjectByUuid(options.uuid);
1188
+ if (!object || !isEditableObject(object)) {
1189
+ throw new Error('当前节点不允许编辑');
1190
+ }
1191
+ const commands = [];
1192
+ if (options.name !== undefined) {
1193
+ commands.push(new SetValueCommand(this, options.uuid, 'name', options.name));
1194
+ }
1195
+ if (options.visible !== undefined) {
1196
+ commands.push(new SetValueCommand(this, options.uuid, 'visible', !!options.visible));
1197
+ }
1198
+ if (Array.isArray(options.position)) {
1199
+ commands.push(new SetPositionCommand(this, options.uuid, options.position));
1200
+ }
1201
+ if (Array.isArray(options.rotation)) {
1202
+ commands.push(new SetRotationCommand(this, options.uuid, options.rotation));
1203
+ }
1204
+ if (Array.isArray(options.scale)) {
1205
+ commands.push(new SetScaleCommand(this, options.uuid, options.scale));
1206
+ }
1207
+ if (options.material && options.material.color !== undefined) {
1208
+ commands.push(
1209
+ new SetValueCommand(this, options.uuid, 'material.color', options.material.color)
1210
+ );
1211
+ }
1212
+ if (options.material && options.material.opacity !== undefined) {
1213
+ commands.push(
1214
+ new SetValueCommand(
1215
+ this,
1216
+ options.uuid,
1217
+ 'material.opacity',
1218
+ Number(options.material.opacity)
1219
+ )
1220
+ );
1221
+ }
1222
+ if (!object.isGroup && options.geometryParams) {
1223
+ commands.push(new SetGeometryParamsCommand(this, options.uuid, options.geometryParams));
1224
+ }
1225
+ if (commands.length === 0) {
1226
+ return object;
1227
+ }
1228
+ const command =
1229
+ commands.length === 1
1230
+ ? commands[0]
1231
+ : new MultiCommand(this, commands, { name: '批量更新元素' });
1232
+ return this.executeCommand(command);
1233
+ }
1234
+
1235
+ updateOriginalModelStyle(options = {}) {
1236
+ const object = this.getObjectByUuid(options.uuid);
1237
+ if (!object || !isOriginalModelObject(object)) {
1238
+ throw new Error('当前节点不是原始模型节点');
1239
+ }
1240
+ const nextStyle = {};
1241
+ if (options.color !== undefined) {
1242
+ nextStyle.color = options.color;
1243
+ }
1244
+ if (options.opacity !== undefined) {
1245
+ nextStyle.opacity = Number(options.opacity);
1246
+ }
1247
+ if (options.visible !== undefined) {
1248
+ nextStyle.visible = !!options.visible;
1249
+ }
1250
+ if (Object.keys(nextStyle).length === 0) {
1251
+ return object;
1252
+ }
1253
+ const currentStyle = this.getOriginalModelStyleValue(options.uuid);
1254
+ const mergedStyle = this.mergeOriginalModelStyle(currentStyle, nextStyle);
1255
+ if (this.getOriginalModelChangedKeys(currentStyle, mergedStyle).length === 0) {
1256
+ return object;
1257
+ }
1258
+ return this.executeCommand(new SetOriginalModelStyleCommand(this, options.uuid, nextStyle));
1259
+ }
1260
+
1261
+ resetOriginalModelStyle(uuid) {
1262
+ const object = this.getObjectByUuid(uuid);
1263
+ if (!object || !isOriginalModelObject(object)) {
1264
+ throw new Error('当前节点不是原始模型节点');
1265
+ }
1266
+ const baseline = this.ensureOriginalModelBaseline(uuid);
1267
+ const currentStyle = this.getOriginalModelStyleValue(uuid);
1268
+ if (this.getOriginalModelChangedKeys(baseline, currentStyle).length === 0) {
1269
+ return object;
1270
+ }
1271
+ return this.executeCommand(new ResetOriginalModelStyleCommand(this, uuid));
1272
+ }
1273
+
1274
+ setElementPosition(uuid, position, oldPosition) {
1275
+ return this.executeCommand(new SetPositionCommand(this, uuid, position, oldPosition));
1276
+ }
1277
+
1278
+ setElementRotation(uuid, rotation, oldRotation) {
1279
+ return this.executeCommand(new SetRotationCommand(this, uuid, rotation, oldRotation));
1280
+ }
1281
+
1282
+ setElementScale(uuid, scale, oldScale) {
1283
+ return this.executeCommand(new SetScaleCommand(this, uuid, scale, oldScale));
1284
+ }
1285
+
1286
+ renameElement(uuid, name) {
1287
+ return this.executeCommand(new SetValueCommand(this, uuid, 'name', name));
1288
+ }
1289
+
1290
+ setElementVisible(uuid, visible) {
1291
+ return this.executeCommand(new SetValueCommand(this, uuid, 'visible', !!visible));
1292
+ }
1293
+
1294
+ undo() {
1295
+ const command = this.history.undo();
1296
+ if (!command) return null;
1297
+ this.requestRender();
1298
+ this.emitCommandEvents(command, 'undo');
1299
+ return command.getObject ? command.getObject() : null;
1300
+ }
1301
+
1302
+ redo() {
1303
+ const command = this.history.redo();
1304
+ if (!command) return null;
1305
+ this.requestRender();
1306
+ this.emitCommandEvents(command, 'redo');
1307
+ return command.getObject ? command.getObject() : null;
1308
+ }
1309
+
1310
+ getCustomTree() {
1311
+ return buildCustomTree(this.getCustomRoot());
1312
+ }
1313
+
1314
+ getCustomSaveTree() {
1315
+ return buildCustomSaveTree(this.getCustomRoot());
1316
+ }
1317
+
1318
+ buildOriginalModelSaveItem(record) {
1319
+ const styleRecord = this.buildOriginalModelStyleRecord(record);
1320
+ if (!styleRecord) return null;
1321
+ return {
1322
+ name: styleRecord.name || '',
1323
+ nodeName: styleRecord.nodeName || styleRecord.name || '',
1324
+ nodeNameReady: true,
1325
+ nodeType: SCENE_NODE_TYPE.ORIGINAL_MODEL,
1326
+ changedKeys: styleRecord.changedKeys.slice(),
1327
+ color: styleRecord.after.color,
1328
+ opacity: styleRecord.after.opacity,
1329
+ visible: styleRecord.after.visible,
1330
+ };
1331
+ }
1332
+
1333
+ getOriginalModelSaveChanges() {
1334
+ return this.getOriginalModelStyleRecords()
1335
+ .map(item => this.buildOriginalModelSaveItem(item))
1336
+ .filter(Boolean);
1337
+ }
1338
+
1339
+ getSaveSnapshot() {
1340
+ return {
1341
+ customModelTree: this.getCustomSaveTree(),
1342
+ originalModelList: this.getOriginalModelSaveChanges(),
1343
+ };
1344
+ }
1345
+
1346
+ createCustomObjectFromSaveNode(node, parent) {
1347
+ if (!node || !parent) return null;
1348
+ const normalizedNode = this.normalizeCustomSaveNode(node);
1349
+ const nodeType = normalizedNode.nodeType || '';
1350
+ if (nodeType === SCENE_NODE_TYPE.CUSTOM_GROUP) {
1351
+ const group = createGroupElement(this.THREE, parent, {
1352
+ name: normalizedNode.name || '',
1353
+ transform: {
1354
+ position: normalizedNode.position,
1355
+ rotation: normalizedNode.rotation,
1356
+ scale: normalizedNode.scale,
1357
+ },
1358
+ source: 'load',
1359
+ });
1360
+ group.visible = normalizedNode.visible !== false;
1361
+ return group;
1362
+ }
1363
+ if (nodeType === SCENE_NODE_TYPE.CUSTOM_ELEMENT) {
1364
+ const element = createElementByType(this.THREE, node.elementType, parent, {
1365
+ name: normalizedNode.name || '',
1366
+ transform: {
1367
+ position: normalizedNode.position,
1368
+ rotation: normalizedNode.rotation,
1369
+ scale: normalizedNode.scale,
1370
+ },
1371
+ geometryParams: normalizedNode.geometryParams || {},
1372
+ material: normalizedNode.material || {},
1373
+ source: 'load',
1374
+ });
1375
+ element.visible = normalizedNode.visible !== false;
1376
+ return element;
1377
+ }
1378
+ return null;
1379
+ }
1380
+
1381
+ restoreCustomSaveTree(snapshot) {
1382
+ const root = this.getCustomRoot();
1383
+ while (root.children.length > 0) {
1384
+ const child = root.children[0];
1385
+ root.remove(child);
1386
+ disposeObjectResources(child);
1387
+ }
1388
+ const sourceChildren =
1389
+ snapshot && Array.isArray(snapshot.children)
1390
+ ? snapshot.children
1391
+ : Array.isArray(snapshot)
1392
+ ? snapshot
1393
+ : [];
1394
+ const appendChildren = (children, parent) => {
1395
+ children.forEach(childNode => {
1396
+ const object = this.createCustomObjectFromSaveNode(childNode, parent);
1397
+ if (!object) return;
1398
+ insertObjectAt(parent, object, parent.children.length);
1399
+ if (Array.isArray(childNode.children) && childNode.children.length > 0) {
1400
+ appendChildren(childNode.children, object);
1401
+ }
1402
+ });
1403
+ };
1404
+ appendChildren(sourceChildren, root);
1405
+ this.normalizeCustomObjectFlags(root);
1406
+ this.normalizeCustomElementGeometryParams(root);
1407
+ return root;
1408
+ }
1409
+
1410
+ restoreOriginalModelSaveChanges(changes = []) {
1411
+ const nextChanges = Array.isArray(changes)
1412
+ ? changes.map(item => this.normalizeOriginalModelSaveItem(item))
1413
+ : [];
1414
+ this.getOriginalModelStyleRecords().forEach(record => {
1415
+ const object = this.resolveOriginalModelObject(record);
1416
+ const baseline = this.getOriginalModelBaseline(record);
1417
+ if (!baseline || !object || !isOriginalModelObject(object)) {
1418
+ return;
1419
+ }
1420
+ this.applyOriginalModelStyle(object.uuid, baseline);
1421
+ });
1422
+ this.originalModelStyleChanges.clear();
1423
+ nextChanges.forEach(item => {
1424
+ this.setOriginalModelStyleRecord(item);
1425
+ });
1426
+ this.retryPendingOriginalModelSaveChanges();
1427
+ }
1428
+
1429
+ retryPendingOriginalModelSaveChanges() {
1430
+ const retryChanges = this.getOriginalModelStyleRecords();
1431
+ if (retryChanges.length === 0) {
1432
+ return [];
1433
+ }
1434
+ const unresolved = [];
1435
+ let shouldRender = false;
1436
+ retryChanges.forEach(item => {
1437
+ if (!item || !item.after) return;
1438
+ const object = this.resolveOriginalModelObject(item);
1439
+ if (!object || !isOriginalModelObject(object)) {
1440
+ unresolved.push(item);
1441
+ return;
1442
+ }
1443
+ const targetStyle = this.cloneOriginalModelStyle(item.after);
1444
+ const currentStyle = this.getOriginalModelStyleValue(object.uuid);
1445
+ this.ensureOriginalModelBaseline(object.uuid);
1446
+ if (!this.isSameOriginalModelStyle(currentStyle, targetStyle)) {
1447
+ this.applyOriginalModelStyle(object.uuid, targetStyle);
1448
+ shouldRender = true;
1449
+ }
1450
+ this.syncOriginalModelStyleRecord(object.uuid, {
1451
+ updatedAt: item.updatedAt,
1452
+ });
1453
+ });
1454
+ if (shouldRender) {
1455
+ this.requestRender();
1456
+ }
1457
+ return unresolved.slice();
1458
+ }
1459
+
1460
+ applySaveSnapshot(snapshot = {}) {
1461
+ const payload = this.normalizeSaveSnapshot(snapshot || {});
1462
+ const hasCustomModelTree = Object.prototype.hasOwnProperty.call(payload, 'customModelTree');
1463
+ const hasOriginalModelChanges = Object.prototype.hasOwnProperty.call(
1464
+ payload,
1465
+ 'originalModelList'
1466
+ );
1467
+ if (hasCustomModelTree) {
1468
+ this.restoreCustomSaveTree(payload.customModelTree);
1469
+ }
1470
+ if (hasOriginalModelChanges) {
1471
+ this.restoreOriginalModelSaveChanges(payload.originalModelList);
1472
+ }
1473
+ this.history.clear();
1474
+ this.clearSelection();
1475
+ this.eventBridge.emitSceneGraphChanged();
1476
+ this.eventBridge.emitOriginalModelChangeListChanged();
1477
+ this.emitHistoryChanged('reset', {
1478
+ type: 'ApplySaveSnapshot',
1479
+ name: '回放技改方案',
1480
+ });
1481
+ this.requestRender();
1482
+ return this.getSaveSnapshot();
1483
+ }
1484
+
1485
+ serializeCustomElements() {
1486
+ return this.getCustomRoot().toJSON();
1487
+ }
1488
+
1489
+ deserializeCustomElements(json) {
1490
+ if (!json) return null;
1491
+ const root = this.getCustomRoot();
1492
+ while (root.children.length > 0) {
1493
+ const child = root.children[0];
1494
+ root.remove(child);
1495
+ disposeObjectResources(child);
1496
+ }
1497
+ const parsedRoot = this.parseObjectJSON(json);
1498
+ while (parsedRoot.children.length > 0) {
1499
+ const child = parsedRoot.children.shift();
1500
+ insertObjectAt(root, child, root.children.length);
1501
+ }
1502
+ this.normalizeCustomObjectFlags(root);
1503
+ this.normalizeCustomElementGeometryParams(root);
1504
+ this.history.clear();
1505
+ this.clearSelection();
1506
+ this.eventBridge.emitSceneGraphChanged();
1507
+ this.emitHistoryChanged('reset', {
1508
+ type: 'DeserializeCustomElements',
1509
+ name: '反序列化自定义元素',
1510
+ });
1511
+ return root;
1512
+ }
1513
+
1514
+ removeAllCustomElements() {
1515
+ const root = this.getCustomRoot();
1516
+ while (root.children.length > 0) {
1517
+ const child = root.children[0];
1518
+ root.remove(child);
1519
+ disposeObjectResources(child);
1520
+ }
1521
+ this.history.clear();
1522
+ this.clearSelection();
1523
+ this.eventBridge.emitSceneGraphChanged();
1524
+ this.emitHistoryChanged('reset', {
1525
+ type: 'ClearCustomElements',
1526
+ name: '清空自定义元素',
1527
+ });
1528
+ }
1529
+ }